Add AutoValue classes for Preferences

AccountState is a cached entity that contains user preferences. Any
cached entity should only hold onto immutable objects and be immutable
itself. This commit addds AutoValue classes for user preferences that
replace the *Info objects in AccountState.

For now, the change is internal to AccountState only and the rest of the
code is not touched. In the future, we might keep *Info classes only on
the API level and use the immutable AutoValue representation in the
inner works of Gerrit. Any such change can be an interative follow up.

We use Optionals for now to express that any field can be unset. The
alternative here is @Nullable. For now, Optional seems to be the better
choice as it makes callers explicitly aware of that fact. This can be
adapted any time.

The alternative to making these classes AutoValues would be either to
defensively copy them when storing and retrieving them from AccountState
or using Protobuf. In the former case, the required boiler plate would
be similar to what is implemented here as we would need copy methods as
well as equals and hashCode. The latter case is very similar to
AutoValues, but we don't pass protos around in Gerrit until now.

This change explicitly resets the user in two test cases as the test
suite holds on to an IdentifiedUser which in turn holds on to an
AccountState. Prior to this commit, we would mutate these values when
updating the preferences. With the new mechanism, that is not the case
anymore (this is the correct state of the world).

Change-Id: I8ab306e8d1d8f5d1d9ce3209aaa600347ed47e5c
This commit is contained in:
Patrick Hiesel
2019-08-09 10:21:48 +02:00
parent b89e20a150
commit 0d9c7e2894
5 changed files with 540 additions and 10 deletions

View File

@@ -131,9 +131,9 @@ public class AccountState {
private final ImmutableSet<ExternalId> externalIds;
private final Optional<String> userName;
private final ImmutableMap<ProjectWatchKey, ImmutableSet<NotifyType>> projectWatches;
private final GeneralPreferencesInfo generalPreferences;
private final DiffPreferencesInfo diffPreferences;
private final EditPreferencesInfo editPreferences;
private final Preferences.General generalPreferences;
private final Preferences.Diff diffPreferences;
private final Preferences.Edit editPreferences;
private AccountState(
Account account,
@@ -146,9 +146,9 @@ public class AccountState {
this.externalIds = externalIds;
this.userName = ExternalId.getUserName(externalIds);
this.projectWatches = projectWatches;
this.generalPreferences = generalPreferences;
this.diffPreferences = diffPreferences;
this.editPreferences = editPreferences;
this.generalPreferences = Preferences.General.fromInfo(generalPreferences);
this.diffPreferences = Preferences.Diff.fromInfo(diffPreferences);
this.editPreferences = Preferences.Edit.fromInfo(editPreferences);
}
/** Get the cached account metadata. */
@@ -180,17 +180,17 @@ public class AccountState {
/** The general preferences of the account. */
public GeneralPreferencesInfo getGeneralPreferences() {
return generalPreferences;
return generalPreferences.toInfo();
}
/** The diff preferences of the account. */
public DiffPreferencesInfo getDiffPreferences() {
return diffPreferences;
return diffPreferences.toInfo();
}
/** The edit preferences of the account. */
public EditPreferencesInfo getEditPreferences() {
return editPreferences;
return editPreferences.toInfo();
}
@Override

View File

@@ -0,0 +1,463 @@
// Copyright (C) 2019 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.account;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
import com.google.gerrit.extensions.client.EditPreferencesInfo;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DateFormat;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DefaultBase;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DiffView;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DownloadCommand;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailFormat;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailStrategy;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.TimeFormat;
import com.google.gerrit.extensions.client.KeyMapType;
import com.google.gerrit.extensions.client.MenuItem;
import com.google.gerrit.extensions.client.Theme;
import java.util.Optional;
@AutoValue
public abstract class Preferences {
@AutoValue
public abstract static class General {
public abstract Optional<Integer> changesPerPage();
public abstract Optional<String> downloadScheme();
public abstract Optional<DownloadCommand> downloadCommand();
public abstract Optional<DateFormat> dateFormat();
public abstract Optional<TimeFormat> timeFormat();
public abstract Optional<Boolean> expandInlineDiffs();
public abstract Optional<Boolean> highlightAssigneeInChangeTable();
public abstract Optional<Boolean> relativeDateInChangeTable();
public abstract Optional<DiffView> diffView();
public abstract Optional<Boolean> sizeBarInChangeTable();
public abstract Optional<Boolean> legacycidInChangeTable();
public abstract Optional<Boolean> muteCommonPathPrefixes();
public abstract Optional<Boolean> signedOffBy();
public abstract Optional<EmailStrategy> emailStrategy();
public abstract Optional<EmailFormat> emailFormat();
public abstract Optional<DefaultBase> defaultBaseForMerges();
public abstract Optional<Boolean> publishCommentsOnPush();
public abstract Optional<Boolean> workInProgressByDefault();
public abstract Optional<ImmutableList<MenuItem>> my();
public abstract Optional<ImmutableList<String>> changeTable();
public abstract Optional<ImmutableMap<String, String>> urlAliases();
@AutoValue.Builder
public abstract static class Builder {
abstract Builder changesPerPage(@Nullable Integer val);
abstract Builder downloadScheme(@Nullable String val);
abstract Builder downloadCommand(@Nullable DownloadCommand val);
abstract Builder dateFormat(@Nullable DateFormat val);
abstract Builder timeFormat(@Nullable TimeFormat val);
abstract Builder expandInlineDiffs(@Nullable Boolean val);
abstract Builder highlightAssigneeInChangeTable(@Nullable Boolean val);
abstract Builder relativeDateInChangeTable(@Nullable Boolean val);
abstract Builder diffView(@Nullable DiffView val);
abstract Builder sizeBarInChangeTable(@Nullable Boolean val);
abstract Builder legacycidInChangeTable(@Nullable Boolean val);
abstract Builder muteCommonPathPrefixes(@Nullable Boolean val);
abstract Builder signedOffBy(@Nullable Boolean val);
abstract Builder emailStrategy(@Nullable EmailStrategy val);
abstract Builder emailFormat(@Nullable EmailFormat val);
abstract Builder defaultBaseForMerges(@Nullable DefaultBase val);
abstract Builder publishCommentsOnPush(@Nullable Boolean val);
abstract Builder workInProgressByDefault(@Nullable Boolean val);
abstract Builder my(@Nullable ImmutableList<MenuItem> val);
abstract Builder changeTable(@Nullable ImmutableList<String> val);
abstract Builder urlAliases(@Nullable ImmutableMap<String, String> val);
abstract General build();
}
public static General fromInfo(GeneralPreferencesInfo info) {
return (new AutoValue_Preferences_General.Builder())
.changesPerPage(info.changesPerPage)
.downloadScheme(info.downloadScheme)
.downloadCommand(info.downloadCommand)
.dateFormat(info.dateFormat)
.timeFormat(info.timeFormat)
.expandInlineDiffs(info.expandInlineDiffs)
.highlightAssigneeInChangeTable(info.highlightAssigneeInChangeTable)
.relativeDateInChangeTable(info.relativeDateInChangeTable)
.diffView(info.diffView)
.sizeBarInChangeTable(info.sizeBarInChangeTable)
.legacycidInChangeTable(info.legacycidInChangeTable)
.muteCommonPathPrefixes(info.muteCommonPathPrefixes)
.signedOffBy(info.signedOffBy)
.emailStrategy(info.emailStrategy)
.emailFormat(info.emailFormat)
.defaultBaseForMerges(info.defaultBaseForMerges)
.publishCommentsOnPush(info.publishCommentsOnPush)
.workInProgressByDefault(info.workInProgressByDefault)
.my(info.my == null ? null : ImmutableList.copyOf(info.my))
.changeTable(info.changeTable == null ? null : ImmutableList.copyOf(info.changeTable))
.urlAliases(info.urlAliases == null ? null : ImmutableMap.copyOf(info.urlAliases))
.build();
}
public GeneralPreferencesInfo toInfo() {
GeneralPreferencesInfo info = new GeneralPreferencesInfo();
info.changesPerPage = changesPerPage().orElse(null);
info.downloadScheme = downloadScheme().orElse(null);
info.downloadCommand = downloadCommand().orElse(null);
info.dateFormat = dateFormat().orElse(null);
info.timeFormat = timeFormat().orElse(null);
info.expandInlineDiffs = expandInlineDiffs().orElse(null);
info.highlightAssigneeInChangeTable = highlightAssigneeInChangeTable().orElse(null);
info.relativeDateInChangeTable = relativeDateInChangeTable().orElse(null);
info.diffView = diffView().orElse(null);
info.sizeBarInChangeTable = sizeBarInChangeTable().orElse(null);
info.legacycidInChangeTable = legacycidInChangeTable().orElse(null);
info.muteCommonPathPrefixes = muteCommonPathPrefixes().orElse(null);
info.signedOffBy = signedOffBy().orElse(null);
info.emailStrategy = emailStrategy().orElse(null);
info.emailFormat = emailFormat().orElse(null);
info.defaultBaseForMerges = defaultBaseForMerges().orElse(null);
info.publishCommentsOnPush = publishCommentsOnPush().orElse(null);
info.workInProgressByDefault = workInProgressByDefault().orElse(null);
info.my = my().orElse(null);
info.changeTable = changeTable().orElse(null);
info.urlAliases = urlAliases().orElse(null);
return info;
}
}
@AutoValue
public abstract static class Edit {
public abstract Optional<Integer> tabSize();
public abstract Optional<Integer> lineLength();
public abstract Optional<Integer> indentUnit();
public abstract Optional<Integer> cursorBlinkRate();
public abstract Optional<Boolean> hideTopMenu();
public abstract Optional<Boolean> showTabs();
public abstract Optional<Boolean> showWhitespaceErrors();
public abstract Optional<Boolean> syntaxHighlighting();
public abstract Optional<Boolean> hideLineNumbers();
public abstract Optional<Boolean> matchBrackets();
public abstract Optional<Boolean> lineWrapping();
public abstract Optional<Boolean> indentWithTabs();
public abstract Optional<Boolean> autoCloseBrackets();
public abstract Optional<Boolean> showBase();
public abstract Optional<Theme> theme();
public abstract Optional<KeyMapType> keyMapType();
@AutoValue.Builder
public abstract static class Builder {
abstract Builder tabSize(@Nullable Integer val);
abstract Builder lineLength(@Nullable Integer val);
abstract Builder indentUnit(@Nullable Integer val);
abstract Builder cursorBlinkRate(@Nullable Integer val);
abstract Builder hideTopMenu(@Nullable Boolean val);
abstract Builder showTabs(@Nullable Boolean val);
abstract Builder showWhitespaceErrors(@Nullable Boolean val);
abstract Builder syntaxHighlighting(@Nullable Boolean val);
abstract Builder hideLineNumbers(@Nullable Boolean val);
abstract Builder matchBrackets(@Nullable Boolean val);
abstract Builder lineWrapping(@Nullable Boolean val);
abstract Builder indentWithTabs(@Nullable Boolean val);
abstract Builder autoCloseBrackets(@Nullable Boolean val);
abstract Builder showBase(@Nullable Boolean val);
abstract Builder theme(@Nullable Theme val);
abstract Builder keyMapType(@Nullable KeyMapType val);
abstract Edit build();
}
public static Edit fromInfo(EditPreferencesInfo info) {
return (new AutoValue_Preferences_Edit.Builder())
.tabSize(info.tabSize)
.lineLength(info.lineLength)
.indentUnit(info.indentUnit)
.cursorBlinkRate(info.cursorBlinkRate)
.hideTopMenu(info.hideTopMenu)
.showTabs(info.showTabs)
.showWhitespaceErrors(info.showWhitespaceErrors)
.syntaxHighlighting(info.syntaxHighlighting)
.hideLineNumbers(info.hideLineNumbers)
.matchBrackets(info.matchBrackets)
.lineWrapping(info.lineWrapping)
.indentWithTabs(info.indentWithTabs)
.autoCloseBrackets(info.autoCloseBrackets)
.showBase(info.showBase)
.theme(info.theme)
.keyMapType(info.keyMapType)
.build();
}
public EditPreferencesInfo toInfo() {
EditPreferencesInfo info = new EditPreferencesInfo();
info.tabSize = tabSize().orElse(null);
info.lineLength = lineLength().orElse(null);
info.indentUnit = indentUnit().orElse(null);
info.cursorBlinkRate = cursorBlinkRate().orElse(null);
info.hideTopMenu = hideTopMenu().orElse(null);
info.showTabs = showTabs().orElse(null);
info.showWhitespaceErrors = showWhitespaceErrors().orElse(null);
info.syntaxHighlighting = syntaxHighlighting().orElse(null);
info.hideLineNumbers = hideLineNumbers().orElse(null);
info.matchBrackets = matchBrackets().orElse(null);
info.lineWrapping = lineWrapping().orElse(null);
info.indentWithTabs = indentWithTabs().orElse(null);
info.autoCloseBrackets = autoCloseBrackets().orElse(null);
info.showBase = showBase().orElse(null);
info.theme = theme().orElse(null);
info.keyMapType = keyMapType().orElse(null);
return info;
}
}
@AutoValue
public abstract static class Diff {
public abstract Optional<Integer> context();
public abstract Optional<Integer> tabSize();
public abstract Optional<Integer> fontSize();
public abstract Optional<Integer> lineLength();
public abstract Optional<Integer> cursorBlinkRate();
public abstract Optional<Boolean> expandAllComments();
public abstract Optional<Boolean> intralineDifference();
public abstract Optional<Boolean> manualReview();
public abstract Optional<Boolean> showLineEndings();
public abstract Optional<Boolean> showTabs();
public abstract Optional<Boolean> showWhitespaceErrors();
public abstract Optional<Boolean> syntaxHighlighting();
public abstract Optional<Boolean> hideTopMenu();
public abstract Optional<Boolean> autoHideDiffTableHeader();
public abstract Optional<Boolean> hideLineNumbers();
public abstract Optional<Boolean> renderEntireFile();
public abstract Optional<Boolean> hideEmptyPane();
public abstract Optional<Boolean> matchBrackets();
public abstract Optional<Boolean> lineWrapping();
public abstract Optional<Theme> theme();
public abstract Optional<Whitespace> ignoreWhitespace();
public abstract Optional<Boolean> retainHeader();
public abstract Optional<Boolean> skipDeleted();
public abstract Optional<Boolean> skipUnchanged();
public abstract Optional<Boolean> skipUncommented();
@AutoValue.Builder
public abstract static class Builder {
abstract Builder context(@Nullable Integer val);
abstract Builder tabSize(@Nullable Integer val);
abstract Builder fontSize(@Nullable Integer val);
abstract Builder lineLength(@Nullable Integer val);
abstract Builder cursorBlinkRate(@Nullable Integer val);
abstract Builder expandAllComments(@Nullable Boolean val);
abstract Builder intralineDifference(@Nullable Boolean val);
abstract Builder manualReview(@Nullable Boolean val);
abstract Builder showLineEndings(@Nullable Boolean val);
abstract Builder showTabs(@Nullable Boolean val);
abstract Builder showWhitespaceErrors(@Nullable Boolean val);
abstract Builder syntaxHighlighting(@Nullable Boolean val);
abstract Builder hideTopMenu(@Nullable Boolean val);
abstract Builder autoHideDiffTableHeader(@Nullable Boolean val);
abstract Builder hideLineNumbers(@Nullable Boolean val);
abstract Builder renderEntireFile(@Nullable Boolean val);
abstract Builder hideEmptyPane(@Nullable Boolean val);
abstract Builder matchBrackets(@Nullable Boolean val);
abstract Builder lineWrapping(@Nullable Boolean val);
abstract Builder theme(@Nullable Theme val);
abstract Builder ignoreWhitespace(@Nullable Whitespace val);
abstract Builder retainHeader(@Nullable Boolean val);
abstract Builder skipDeleted(@Nullable Boolean val);
abstract Builder skipUnchanged(@Nullable Boolean val);
abstract Builder skipUncommented(@Nullable Boolean val);
abstract Diff build();
}
public static Diff fromInfo(DiffPreferencesInfo info) {
return (new AutoValue_Preferences_Diff.Builder())
.context(info.context)
.tabSize(info.tabSize)
.fontSize(info.fontSize)
.lineLength(info.lineLength)
.cursorBlinkRate(info.cursorBlinkRate)
.expandAllComments(info.expandAllComments)
.intralineDifference(info.intralineDifference)
.manualReview(info.manualReview)
.showLineEndings(info.showLineEndings)
.showTabs(info.showTabs)
.showWhitespaceErrors(info.showWhitespaceErrors)
.syntaxHighlighting(info.syntaxHighlighting)
.hideTopMenu(info.hideTopMenu)
.autoHideDiffTableHeader(info.autoHideDiffTableHeader)
.hideLineNumbers(info.hideLineNumbers)
.renderEntireFile(info.renderEntireFile)
.hideEmptyPane(info.hideEmptyPane)
.matchBrackets(info.matchBrackets)
.lineWrapping(info.lineWrapping)
.theme(info.theme)
.ignoreWhitespace(info.ignoreWhitespace)
.retainHeader(info.retainHeader)
.skipDeleted(info.skipDeleted)
.skipUnchanged(info.skipUnchanged)
.skipUncommented(info.skipUncommented)
.build();
}
public DiffPreferencesInfo toInfo() {
DiffPreferencesInfo info = new DiffPreferencesInfo();
info.context = context().orElse(null);
info.tabSize = tabSize().orElse(null);
info.fontSize = fontSize().orElse(null);
info.lineLength = lineLength().orElse(null);
info.cursorBlinkRate = cursorBlinkRate().orElse(null);
info.expandAllComments = expandAllComments().orElse(null);
info.intralineDifference = intralineDifference().orElse(null);
info.manualReview = manualReview().orElse(null);
info.showLineEndings = showLineEndings().orElse(null);
info.showTabs = showTabs().orElse(null);
info.showWhitespaceErrors = showWhitespaceErrors().orElse(null);
info.syntaxHighlighting = syntaxHighlighting().orElse(null);
info.hideTopMenu = hideTopMenu().orElse(null);
info.autoHideDiffTableHeader = autoHideDiffTableHeader().orElse(null);
info.hideLineNumbers = hideLineNumbers().orElse(null);
info.renderEntireFile = renderEntireFile().orElse(null);
info.hideEmptyPane = hideEmptyPane().orElse(null);
info.matchBrackets = matchBrackets().orElse(null);
info.lineWrapping = lineWrapping().orElse(null);
info.theme = theme().orElse(null);
info.ignoreWhitespace = ignoreWhitespace().orElse(null);
info.retainHeader = retainHeader().orElse(null);
info.skipDeleted = skipDeleted().orElse(null);
info.skipUnchanged = skipUnchanged().orElse(null);
info.skipUncommented = skipUncommented().orElse(null);
return info;
}
}
}

View File

@@ -22,12 +22,16 @@ import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.extensions.api.projects.ConfigInput;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeInput;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.inject.Inject;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
@@ -37,6 +41,7 @@ import org.junit.Test;
public class WorkInProgressByDefaultIT extends AbstractDaemonTest {
@Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
@Test
public void createChangeWithWorkInProgressByDefaultForProjectDisabled() throws Exception {
@@ -175,6 +180,14 @@ public class WorkInProgressByDefaultIT extends AbstractDaemonTest {
setWorkInProgressByDefaultForUser();
// Clone the repo again. The test connection keeps an AccountState internally, so we need to
// create a new connection after changing account properties.
PatchSet.Id ps1OfChange1 =
PatchSet.id(Change.id(gApi.changes().id(changeId1).get()._number), 1);
testRepo = cloneProject(project);
testRepo.git().fetch().setRefSpecs(RefNames.patchSetRef(ps1OfChange1) + ":c1").call();
testRepo.reset("c1");
// Create a new patch set on the existing change and in the same push create a new successor
// change.
RevCommit commit1b = testRepo.amend(commit1a).create();
@@ -199,9 +212,12 @@ public class WorkInProgressByDefaultIT extends AbstractDaemonTest {
}
private void setWorkInProgressByDefaultForUser() throws Exception {
GeneralPreferencesInfo prefs = gApi.accounts().id(admin.id().get()).getPreferences();
GeneralPreferencesInfo prefs = new GeneralPreferencesInfo();
prefs.workInProgressByDefault = true;
gApi.accounts().id(admin.id().get()).setPreferences(prefs);
// Generate a new API scope. User preferences are stored in IdentifiedUser, so we need to flush
// that entity.
requestScopeOperations.resetCurrentApiUser();
}
private PushOneCommit.Result createChange(Project.NameKey p) throws Exception {

View File

@@ -45,6 +45,7 @@ junit_tests(
"//java/com/google/gerrit/index",
"//java/com/google/gerrit/index:query_exception",
"//java/com/google/gerrit/jgit",
"//java/com/google/gerrit/json",
"//java/com/google/gerrit/lifecycle",
"//java/com/google/gerrit/mail",
"//java/com/google/gerrit/metrics",

View File

@@ -0,0 +1,50 @@
// Copyright (C) 2019 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.account;
import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
import com.google.gerrit.extensions.client.EditPreferencesInfo;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
import com.google.gerrit.json.OutputFormat;
import com.google.gson.Gson;
import org.junit.Test;
public class PreferencesTest {
private static final Gson GSON = OutputFormat.JSON_COMPACT.newGson();
@Test
public void generalPreferencesRoundTrip() {
GeneralPreferencesInfo original = GeneralPreferencesInfo.defaults();
assertThat(GSON.toJson(original))
.isEqualTo(GSON.toJson(Preferences.General.fromInfo(original).toInfo()));
}
@Test
public void diffPreferencesRoundTrip() {
DiffPreferencesInfo original = DiffPreferencesInfo.defaults();
assertThat(GSON.toJson(original))
.isEqualTo(GSON.toJson(Preferences.Diff.fromInfo(original).toInfo()));
}
@Test
public void editPreferencesRoundTrip() {
EditPreferencesInfo original = EditPreferencesInfo.defaults();
assertThat(GSON.toJson(original))
.isEqualTo(GSON.toJson(Preferences.Edit.fromInfo(original).toInfo()));
}
}