LCOV - code coverage report
Current view: top level - acceptance - AbstractDaemonTest.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 748 776 96.4 %
Date: 2022-11-19 15:00:39 Functions: 171 177 96.6 %

          Line data    Source code
       1             : // Copyright (C) 2013 The Android Open Source Project
       2             : //
       3             : // Licensed under the Apache License, Version 2.0 (the "License");
       4             : // you may not use this file except in compliance with the License.
       5             : // You may obtain a copy of the License at
       6             : //
       7             : // http://www.apache.org/licenses/LICENSE-2.0
       8             : //
       9             : // Unless required by applicable law or agreed to in writing, software
      10             : // distributed under the License is distributed on an "AS IS" BASIS,
      11             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             : // See the License for the specific language governing permissions and
      13             : // limitations under the License.
      14             : 
      15             : package com.google.gerrit.acceptance;
      16             : 
      17             : import static com.google.common.base.Preconditions.checkArgument;
      18             : import static com.google.common.collect.ImmutableList.toImmutableList;
      19             : import static com.google.common.truth.OptionalSubject.optionals;
      20             : import static com.google.common.truth.Truth.assertThat;
      21             : import static com.google.common.truth.Truth.assertWithMessage;
      22             : import static com.google.common.truth.Truth8.assertThat;
      23             : import static com.google.common.truth.TruthJUnit.assume;
      24             : import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
      25             : import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
      26             : import static com.google.gerrit.entities.Patch.COMMIT_MSG;
      27             : import static com.google.gerrit.entities.Patch.MERGE_LIST;
      28             : import static com.google.gerrit.extensions.api.changes.SubmittedTogetherOption.NON_VISIBLE_CHANGES;
      29             : import static com.google.gerrit.extensions.api.changes.SubmittedTogetherOption.TOPIC_CLOSURE;
      30             : import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
      31             : import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
      32             : import static com.google.gerrit.server.project.ProjectCache.illegalState;
      33             : import static com.google.gerrit.server.project.testing.TestLabels.label;
      34             : import static com.google.gerrit.server.project.testing.TestLabels.value;
      35             : import static com.google.gerrit.testing.GerritJUnit.assertThrows;
      36             : import static java.nio.charset.StandardCharsets.UTF_8;
      37             : import static java.util.Objects.requireNonNull;
      38             : import static java.util.stream.Collectors.toList;
      39             : import static org.eclipse.jgit.lib.Constants.HEAD;
      40             : 
      41             : import com.github.rholder.retry.BlockStrategy;
      42             : import com.google.common.base.Strings;
      43             : import com.google.common.base.Ticker;
      44             : import com.google.common.collect.ImmutableList;
      45             : import com.google.common.collect.ImmutableMap;
      46             : import com.google.common.collect.ImmutableSet;
      47             : import com.google.common.collect.Iterables;
      48             : import com.google.common.collect.Lists;
      49             : import com.google.common.primitives.Chars;
      50             : import com.google.common.testing.FakeTicker;
      51             : import com.google.gerrit.acceptance.AcceptanceTestRequestScope.Context;
      52             : import com.google.gerrit.acceptance.PushOneCommit.Result;
      53             : import com.google.gerrit.acceptance.testsuite.account.TestSshKeys;
      54             : import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
      55             : import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
      56             : import com.google.gerrit.acceptance.testsuite.request.SshSessionFactory;
      57             : import com.google.gerrit.common.Nullable;
      58             : import com.google.gerrit.entities.AccessSection;
      59             : import com.google.gerrit.entities.Account;
      60             : import com.google.gerrit.entities.AccountGroup;
      61             : import com.google.gerrit.entities.Address;
      62             : import com.google.gerrit.entities.BooleanProjectConfig;
      63             : import com.google.gerrit.entities.BranchNameKey;
      64             : import com.google.gerrit.entities.Change;
      65             : import com.google.gerrit.entities.EmailHeader;
      66             : import com.google.gerrit.entities.EmailHeader.StringEmailHeader;
      67             : import com.google.gerrit.entities.GroupDescription;
      68             : import com.google.gerrit.entities.GroupReference;
      69             : import com.google.gerrit.entities.InternalGroup;
      70             : import com.google.gerrit.entities.LabelFunction;
      71             : import com.google.gerrit.entities.LabelType;
      72             : import com.google.gerrit.entities.LabelValue;
      73             : import com.google.gerrit.entities.PatchSet;
      74             : import com.google.gerrit.entities.Permission;
      75             : import com.google.gerrit.entities.PermissionRule;
      76             : import com.google.gerrit.entities.PermissionRule.Action;
      77             : import com.google.gerrit.entities.Project;
      78             : import com.google.gerrit.entities.RefNames;
      79             : import com.google.gerrit.entities.SubmitRequirement;
      80             : import com.google.gerrit.extensions.api.GerritApi;
      81             : import com.google.gerrit.extensions.api.changes.ChangeApi;
      82             : import com.google.gerrit.extensions.api.changes.ReviewInput;
      83             : import com.google.gerrit.extensions.api.changes.RevisionApi;
      84             : import com.google.gerrit.extensions.api.changes.SubmittedTogetherInfo;
      85             : import com.google.gerrit.extensions.api.changes.SubmittedTogetherOption;
      86             : import com.google.gerrit.extensions.api.projects.BranchApi;
      87             : import com.google.gerrit.extensions.api.projects.BranchInfo;
      88             : import com.google.gerrit.extensions.api.projects.BranchInput;
      89             : import com.google.gerrit.extensions.api.projects.ProjectInput;
      90             : import com.google.gerrit.extensions.client.InheritableBoolean;
      91             : import com.google.gerrit.extensions.client.ListChangesOption;
      92             : import com.google.gerrit.extensions.client.ProjectWatchInfo;
      93             : import com.google.gerrit.extensions.client.ReviewerState;
      94             : import com.google.gerrit.extensions.client.SubmitType;
      95             : import com.google.gerrit.extensions.common.AccountInfo;
      96             : import com.google.gerrit.extensions.common.ChangeInfo;
      97             : import com.google.gerrit.extensions.common.ChangeType;
      98             : import com.google.gerrit.extensions.common.CommentInfo;
      99             : import com.google.gerrit.extensions.common.DiffInfo;
     100             : import com.google.gerrit.extensions.common.EditInfo;
     101             : import com.google.gerrit.extensions.restapi.BinaryResult;
     102             : import com.google.gerrit.extensions.restapi.IdString;
     103             : import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
     104             : import com.google.gerrit.extensions.restapi.RestApiException;
     105             : import com.google.gerrit.json.OutputFormat;
     106             : import com.google.gerrit.server.GerritPersonIdent;
     107             : import com.google.gerrit.server.IdentifiedUser;
     108             : import com.google.gerrit.server.PatchSetUtil;
     109             : import com.google.gerrit.server.PluginUser;
     110             : import com.google.gerrit.server.account.AccountCache;
     111             : import com.google.gerrit.server.account.AccountState;
     112             : import com.google.gerrit.server.account.Accounts;
     113             : import com.google.gerrit.server.account.GroupBackend;
     114             : import com.google.gerrit.server.account.GroupCache;
     115             : import com.google.gerrit.server.account.externalids.ExternalId;
     116             : import com.google.gerrit.server.change.BatchAbandon;
     117             : import com.google.gerrit.server.change.ChangeFinder;
     118             : import com.google.gerrit.server.change.ChangeResource;
     119             : import com.google.gerrit.server.change.FileContentUtil;
     120             : import com.google.gerrit.server.change.RevisionResource;
     121             : import com.google.gerrit.server.config.AllProjectsName;
     122             : import com.google.gerrit.server.config.AllUsersName;
     123             : import com.google.gerrit.server.config.CanonicalWebUrl;
     124             : import com.google.gerrit.server.config.GerritInstanceId;
     125             : import com.google.gerrit.server.config.GerritServerConfig;
     126             : import com.google.gerrit.server.config.PluginConfigFactory;
     127             : import com.google.gerrit.server.config.SitePaths;
     128             : import com.google.gerrit.server.git.GitRepositoryManager;
     129             : import com.google.gerrit.server.git.meta.MetaDataUpdate;
     130             : import com.google.gerrit.server.group.SystemGroupBackend;
     131             : import com.google.gerrit.server.index.account.AccountIndexer;
     132             : import com.google.gerrit.server.index.change.ChangeIndexer;
     133             : import com.google.gerrit.server.notedb.AbstractChangeNotes;
     134             : import com.google.gerrit.server.notedb.ChangeNoteUtil;
     135             : import com.google.gerrit.server.notedb.ChangeNotes;
     136             : import com.google.gerrit.server.notedb.ChangeNotesCommit;
     137             : import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk;
     138             : import com.google.gerrit.server.plugins.PluginGuiceEnvironment;
     139             : import com.google.gerrit.server.plugins.TestServerPlugin;
     140             : import com.google.gerrit.server.project.ProjectCache;
     141             : import com.google.gerrit.server.project.ProjectConfig;
     142             : import com.google.gerrit.server.query.change.ChangeData;
     143             : import com.google.gerrit.server.query.change.InternalChangeQuery;
     144             : import com.google.gerrit.server.restapi.change.Revisions;
     145             : import com.google.gerrit.server.update.BatchUpdate;
     146             : import com.google.gerrit.server.util.git.DelegateSystemReader;
     147             : import com.google.gerrit.testing.ConfigSuite;
     148             : import com.google.gerrit.testing.FakeEmailSender;
     149             : import com.google.gerrit.testing.FakeEmailSender.Message;
     150             : import com.google.gerrit.testing.SshMode;
     151             : import com.google.gerrit.testing.TestTimeUtil;
     152             : import com.google.gson.Gson;
     153             : import com.google.inject.Inject;
     154             : import com.google.inject.Module;
     155             : import com.google.inject.Provider;
     156             : import java.io.ByteArrayOutputStream;
     157             : import java.io.File;
     158             : import java.io.IOException;
     159             : import java.lang.reflect.Modifier;
     160             : import java.sql.Timestamp;
     161             : import java.time.Instant;
     162             : import java.util.ArrayList;
     163             : import java.util.Arrays;
     164             : import java.util.Collection;
     165             : import java.util.Collections;
     166             : import java.util.Comparator;
     167             : import java.util.EnumSet;
     168             : import java.util.HashMap;
     169             : import java.util.List;
     170             : import java.util.Map;
     171             : import java.util.Objects;
     172             : import java.util.Optional;
     173             : import java.util.UUID;
     174             : import java.util.regex.Pattern;
     175             : import java.util.stream.Collectors;
     176             : import java.util.stream.IntStream;
     177             : import org.eclipse.jgit.api.Git;
     178             : import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
     179             : import org.eclipse.jgit.junit.TestRepository;
     180             : import org.eclipse.jgit.lib.Config;
     181             : import org.eclipse.jgit.lib.Constants;
     182             : import org.eclipse.jgit.lib.ObjectId;
     183             : import org.eclipse.jgit.lib.PersonIdent;
     184             : import org.eclipse.jgit.lib.Ref;
     185             : import org.eclipse.jgit.lib.Repository;
     186             : import org.eclipse.jgit.revwalk.RevCommit;
     187             : import org.eclipse.jgit.revwalk.RevSort;
     188             : import org.eclipse.jgit.revwalk.RevTree;
     189             : import org.eclipse.jgit.revwalk.RevWalk;
     190             : import org.eclipse.jgit.storage.file.FileBasedConfig;
     191             : import org.eclipse.jgit.transport.Transport;
     192             : import org.eclipse.jgit.util.FS;
     193             : import org.eclipse.jgit.util.SystemReader;
     194             : import org.junit.After;
     195             : import org.junit.Before;
     196             : import org.junit.ClassRule;
     197             : import org.junit.Rule;
     198             : import org.junit.rules.TemporaryFolder;
     199             : import org.junit.rules.TestRule;
     200             : import org.junit.runner.Description;
     201             : import org.junit.runner.RunWith;
     202             : import org.junit.runners.model.Statement;
     203             : 
     204             : @RunWith(ConfigSuite.class)
     205         132 : public abstract class AbstractDaemonTest {
     206             : 
     207             :   /**
     208             :    * Test methods without special annotations will use a common server for efficiency reasons. The
     209             :    * server is torn down after the test class is done.
     210             :    */
     211             :   private static GerritServer commonServer;
     212             : 
     213             :   private static Description firstTest;
     214             : 
     215         132 :   @ClassRule public static TemporaryFolder temporaryFolder = new TemporaryFolder();
     216             : 
     217             :   @ConfigSuite.Parameter public Config baseConfig;
     218             :   @ConfigSuite.Name private String configName;
     219             : 
     220         132 :   @Rule
     221             :   public TestRule testRunner =
     222         132 :       new TestRule() {
     223             :         @Override
     224             :         public Statement apply(Statement base, Description description) {
     225         132 :           return new Statement() {
     226             :             @Override
     227             :             public void evaluate() throws Throwable {
     228         132 :               if (firstTest == null) {
     229         132 :                 firstTest = description;
     230             :               }
     231         132 :               beforeTest(description);
     232         132 :               ProjectResetter.Config input = requireNonNull(resetProjects());
     233             : 
     234             :               try (ProjectResetter resetter =
     235         132 :                   projectResetter != null ? projectResetter.builder().build(input) : null) {
     236         132 :                 AbstractDaemonTest.this.resetter = resetter;
     237         132 :                 base.evaluate();
     238             :               } finally {
     239         132 :                 AbstractDaemonTest.this.resetter = null;
     240         132 :                 afterTest();
     241             :               }
     242         132 :             }
     243             :           };
     244             :         }
     245             :       };
     246             : 
     247             :   @Inject @CanonicalWebUrl protected Provider<String> canonicalWebUrl;
     248             :   @Inject @GerritPersonIdent protected Provider<PersonIdent> serverIdent;
     249             :   @Inject @GerritServerConfig protected Config cfg;
     250             :   @Inject @GerritInstanceId @Nullable protected String instanceId;
     251             :   @Inject protected AcceptanceTestRequestScope atrScope;
     252             :   @Inject protected AccountCache accountCache;
     253             :   @Inject protected AccountCreator accountCreator;
     254             :   @Inject protected Accounts accounts;
     255             :   @Inject protected AllProjectsName allProjects;
     256             :   @Inject protected AllUsersName allUsers;
     257             :   @Inject protected BatchUpdate.Factory batchUpdateFactory;
     258             :   @Inject protected ChangeData.Factory changeDataFactory;
     259             :   @Inject protected ChangeFinder changeFinder;
     260             :   @Inject protected ChangeIndexer indexer;
     261             :   @Inject protected ChangeNoteUtil changeNoteUtil;
     262             :   @Inject protected ChangeResource.Factory changeResourceFactory;
     263             :   @Inject protected FakeEmailSender sender;
     264             :   @Inject protected GerritApi gApi;
     265             :   @Inject protected GitRepositoryManager repoManager;
     266             :   @Inject protected GroupBackend groupBackend;
     267             :   @Inject protected GroupCache groupCache;
     268             :   @Inject protected IdentifiedUser.GenericFactory identifiedUserFactory;
     269             :   @Inject protected MetaDataUpdate.Server metaDataUpdateFactory;
     270             :   @Inject protected PatchSetUtil psUtil;
     271             :   @Inject protected ProjectCache projectCache;
     272             :   @Inject protected ProjectConfig.Factory projectConfigFactory;
     273             :   @Inject protected ProjectResetter.Builder.Factory projectResetter;
     274             :   @Inject protected Provider<InternalChangeQuery> queryProvider;
     275             :   @Inject protected PushOneCommit.Factory pushFactory;
     276             :   @Inject protected PluginConfigFactory pluginConfig;
     277             :   @Inject protected Revisions revisions;
     278             :   @Inject protected SystemGroupBackend systemGroupBackend;
     279             :   @Inject protected ChangeNotes.Factory notesFactory;
     280             :   @Inject protected BatchAbandon batchAbandon;
     281             :   @Inject protected TestSshKeys sshKeys;
     282             :   @Inject protected TestTicker testTicker;
     283             : 
     284             :   protected EventRecorder eventRecorder;
     285             :   protected GerritServer server;
     286             :   protected Project.NameKey project;
     287             :   protected RestSession adminRestSession;
     288             :   protected RestSession userRestSession;
     289             :   protected RestSession anonymousRestSession;
     290             :   protected SshSession adminSshSession;
     291             :   protected SshSession userSshSession;
     292             :   protected TestAccount admin;
     293             :   protected TestAccount user;
     294             :   protected TestRepository<InMemoryRepository> testRepo;
     295             :   protected String resourcePrefix;
     296             :   protected Description description;
     297             :   protected GerritServer.Description testMethodDescription;
     298             : 
     299             :   protected boolean testRequiresSsh;
     300         132 :   protected BlockStrategy noSleepBlockStrategy = t -> {}; // Don't sleep in tests.
     301             : 
     302             :   @Inject private AbstractChangeNotes.Args changeNotesArgs;
     303             :   @Inject private AccountIndexer accountIndexer;
     304             :   @Inject private EventRecorder.Factory eventRecorderFactory;
     305             :   @Inject private InProcessProtocol inProcessProtocol;
     306             :   @Inject private PluginGuiceEnvironment pluginGuiceEnvironment;
     307             :   @Inject private PluginUser.Factory pluginUserFactory;
     308             :   @Inject private RequestScopeOperations requestScopeOperations;
     309             :   @Inject private SitePaths sitePaths;
     310             :   @Inject private ProjectOperations projectOperations;
     311             : 
     312             :   private ProjectResetter resetter;
     313             :   private List<Repository> toClose;
     314             :   private String systemTimeZone;
     315             :   private SystemReader oldSystemReader;
     316             : 
     317             :   @Before
     318             :   public void clearSender() {
     319         132 :     if (sender != null) {
     320         132 :       sender.clear();
     321             :     }
     322         132 :   }
     323             : 
     324             :   @Before
     325             :   public void startEventRecorder() {
     326         132 :     if (eventRecorderFactory != null) {
     327         132 :       eventRecorder = eventRecorderFactory.create(admin);
     328             :     }
     329         132 :   }
     330             : 
     331             :   @Before
     332             :   public void assumeSshIfRequired() {
     333         132 :     if (testRequiresSsh) {
     334             :       // If the test uses ssh, we use assume() to make sure ssh is enabled on
     335             :       // the test suite. JUnit will skip tests annotated with @UseSsh if we
     336             :       // disable them using the command line flag.
     337          13 :       assume().that(SshMode.useSsh()).isTrue();
     338             :     }
     339         132 :   }
     340             : 
     341             :   @After
     342             :   public void verifyNoPiiInChangeNotes() throws RestApiException, IOException {
     343         132 :     if (testMethodDescription.verifyNoPiiInChangeNotes()) {
     344           4 :       verifyNoAccountDetailsInChangeNotes();
     345             :     }
     346         132 :   }
     347             : 
     348             :   @After
     349             :   public void closeEventRecorder() {
     350         132 :     if (eventRecorder != null) {
     351         132 :       eventRecorder.close();
     352             :     }
     353         132 :   }
     354             : 
     355             :   @ConfigSuite.AfterConfig
     356             :   public static void stopCommonServer() throws Exception {
     357         132 :     if (commonServer != null) {
     358             :       try {
     359         128 :         commonServer.close();
     360           0 :       } catch (Exception e) {
     361           0 :         throw new AssertionError(
     362             :             "Error stopping common server in "
     363           0 :                 + (firstTest != null ? firstTest.getTestClass().getName() : "unknown test class"),
     364             :             e);
     365             :       } finally {
     366         128 :         commonServer = null;
     367             :       }
     368             :     }
     369         132 :   }
     370             : 
     371             :   /** Controls which project and branches should be reset after each test case. */
     372             :   protected ProjectResetter.Config resetProjects() {
     373         131 :     return new ProjectResetter.Config()
     374             :         // Don't reset all refs so that refs/sequences/changes is not touched and change IDs are
     375             :         // not reused.
     376         131 :         .reset(allProjects, RefNames.REFS_CONFIG)
     377             :         // Don't reset refs/sequences/accounts so that account IDs are not reused.
     378         131 :         .reset(
     379             :             allUsers,
     380             :             RefNames.REFS_CONFIG,
     381             :             RefNames.REFS_USERS + "*",
     382             :             RefNames.REFS_EXTERNAL_IDS,
     383             :             RefNames.REFS_GROUPNAMES,
     384             :             RefNames.REFS_GROUPS + "*",
     385             :             RefNames.REFS_STARRED_CHANGES + "*",
     386             :             RefNames.REFS_DRAFT_COMMENTS + "*");
     387             :   }
     388             : 
     389             :   protected void restartAsSlave() throws Exception {
     390           3 :     closeSsh();
     391           3 :     server = GerritServer.restartAsSlave(server);
     392           3 :     server.getTestInjector().injectMembers(this);
     393           3 :     if (resetter != null) {
     394           3 :       server.getTestInjector().injectMembers(resetter);
     395             :     }
     396           3 :     initSsh();
     397           3 :   }
     398             : 
     399             :   protected void restart() throws Exception {
     400           1 :     server = GerritServer.restart(server, createModule(), createSshModule());
     401           1 :     server.getTestInjector().injectMembers(this);
     402           1 :     if (resetter != null) {
     403           1 :       server.getTestInjector().injectMembers(resetter);
     404             :     }
     405           1 :     initSsh();
     406           1 :   }
     407             : 
     408             :   protected void reindexAccount(Account.Id accountId) {
     409         132 :     accountIndexer.index(accountId);
     410         132 :   }
     411             : 
     412             :   protected static Config submitWholeTopicEnabledConfig() {
     413          11 :     Config cfg = new Config();
     414          11 :     cfg.setBoolean("change", null, "submitWholeTopic", true);
     415          11 :     return cfg;
     416             :   }
     417             : 
     418             :   protected boolean isSubmitWholeTopicEnabled() {
     419          10 :     return cfg.getBoolean("change", null, "submitWholeTopic", false);
     420             :   }
     421             : 
     422             :   protected boolean isContributorAgreementsEnabled() {
     423           1 :     return cfg.getBoolean("auth", null, "contributorAgreements", false);
     424             :   }
     425             : 
     426             :   protected void beforeTest(Description description) throws Exception {
     427             :     // SystemReader must be overridden before creating any repos, since they read the user/system
     428             :     // configs at initialization time, and are then stored in the RepositoryCache forever.
     429         132 :     oldSystemReader = setFakeSystemReader(temporaryFolder.getRoot());
     430             : 
     431         132 :     this.description = description;
     432         132 :     GerritServer.Description classDesc =
     433         132 :         GerritServer.Description.forTestClass(description, configName);
     434         132 :     GerritServer.Description methodDesc =
     435         132 :         GerritServer.Description.forTestMethod(description, configName);
     436         132 :     testMethodDescription = methodDesc;
     437             : 
     438         132 :     testRequiresSsh = classDesc.useSshAnnotation() || methodDesc.useSshAnnotation();
     439         132 :     if (!testRequiresSsh) {
     440         125 :       baseConfig.setString("sshd", null, "listenAddress", "off");
     441             :     }
     442             : 
     443         132 :     baseConfig.unset("gerrit", null, "canonicalWebUrl");
     444         132 :     baseConfig.unset("httpd", null, "listenUrl");
     445             : 
     446         132 :     baseConfig.setInt("index", null, "batchThreads", -1);
     447             : 
     448         132 :     Module module = createModule();
     449         132 :     Module auditModule = createAuditModule();
     450         132 :     Module sshModule = createSshModule();
     451         132 :     if (classDesc.equals(methodDesc) && !classDesc.sandboxed() && !methodDesc.sandboxed()) {
     452         128 :       if (commonServer == null) {
     453         128 :         commonServer =
     454         128 :             GerritServer.initAndStart(
     455             :                 temporaryFolder, classDesc, baseConfig, module, auditModule, sshModule);
     456             :       }
     457         128 :       server = commonServer;
     458             :     } else {
     459          53 :       server =
     460          53 :           GerritServer.initAndStart(
     461             :               temporaryFolder, methodDesc, baseConfig, module, auditModule, sshModule);
     462             :     }
     463             : 
     464         132 :     server.getTestInjector().injectMembers(this);
     465         132 :     Transport.register(inProcessProtocol);
     466         132 :     toClose = Collections.synchronizedList(new ArrayList<>());
     467             : 
     468         132 :     admin = accountCreator.admin();
     469         132 :     user = accountCreator.user1();
     470             : 
     471             :     // Evict and reindex accounts in case tests modify them.
     472         132 :     reindexAccount(admin.id());
     473         132 :     reindexAccount(user.id());
     474             : 
     475         132 :     adminRestSession = new RestSession(server, admin);
     476         132 :     userRestSession = new RestSession(server, user);
     477         132 :     anonymousRestSession = new RestSession(server, null);
     478             : 
     479         132 :     initSsh();
     480             : 
     481         132 :     resourcePrefix =
     482             :         UNSAFE_PROJECT_NAME
     483         132 :             .matcher(description.getClassName() + "_" + description.getMethodName() + "_")
     484         132 :             .replaceAll("");
     485             : 
     486         132 :     Context ctx = newRequestContext(admin);
     487         132 :     atrScope.set(ctx);
     488         132 :     ProjectInput in = projectInput(description);
     489         132 :     gApi.projects().create(in);
     490         132 :     project = Project.nameKey(in.name);
     491         132 :     if (!classDesc.skipProjectClone()) {
     492         129 :       testRepo = cloneProject(project, getCloneAsAccount(description));
     493             :     }
     494             : 
     495             :     // Set the clock step last, so that the test setup isn't consuming any timestamps after the
     496             :     // clock has been set.
     497         132 :     setTimeSettings(classDesc.useSystemTime(), classDesc.useClockStep(), classDesc.useTimezone());
     498         132 :     setTimeSettings(
     499         132 :         methodDesc.useSystemTime(), methodDesc.useClockStep(), methodDesc.useTimezone());
     500         132 :   }
     501             : 
     502             :   private static SystemReader setFakeSystemReader(File tempDir) {
     503         132 :     SystemReader oldSystemReader = SystemReader.getInstance();
     504         132 :     SystemReader.setInstance(
     505         132 :         new DelegateSystemReader(oldSystemReader) {
     506             :           @Override
     507             :           public FileBasedConfig openJGitConfig(Config parent, FS fs) {
     508         131 :             return new FileBasedConfig(parent, new File(tempDir, "jgit.config"), FS.detect());
     509             :           }
     510             : 
     511             :           @Override
     512             :           public FileBasedConfig openUserConfig(Config parent, FS fs) {
     513         131 :             return new FileBasedConfig(parent, new File(tempDir, "user.config"), FS.detect());
     514             :           }
     515             : 
     516             :           @Override
     517             :           public FileBasedConfig openSystemConfig(Config parent, FS fs) {
     518         131 :             return new FileBasedConfig(parent, new File(tempDir, "system.config"), FS.detect());
     519             :           }
     520             :         });
     521         132 :     return oldSystemReader;
     522             :   }
     523             : 
     524             :   private void setTimeSettings(
     525             :       boolean useSystemTime,
     526             :       @Nullable UseClockStep useClockStep,
     527             :       @Nullable UseTimezone useTimezone) {
     528         132 :     if (useSystemTime) {
     529           2 :       TestTimeUtil.useSystemTime();
     530         132 :     } else if (useClockStep != null) {
     531          22 :       TestTimeUtil.resetWithClockStep(useClockStep.clockStep(), useClockStep.clockStepUnit());
     532          22 :       if (useClockStep.startAtEpoch()) {
     533           1 :         TestTimeUtil.setClock(Timestamp.from(Instant.EPOCH));
     534             :       }
     535             :     }
     536         132 :     if (useTimezone != null) {
     537          13 :       systemTimeZone = System.setProperty("user.timezone", useTimezone.timezone());
     538             :     }
     539         132 :   }
     540             : 
     541             :   private void resetTimeSettings() {
     542         132 :     TestTimeUtil.useSystemTime();
     543         132 :     if (systemTimeZone != null) {
     544          13 :       System.setProperty("user.timezone", systemTimeZone);
     545          13 :       systemTimeZone = null;
     546             :     }
     547         132 :   }
     548             : 
     549             :   /** Override to bind an additional Guice module */
     550             :   public Module createModule() {
     551         125 :     return null;
     552             :   }
     553             : 
     554             :   /** Override to bind an alternative audit Guice module */
     555             :   public Module createAuditModule() {
     556         132 :     return null;
     557             :   }
     558             : 
     559             :   /** Override to bind an additional Guice module for SSH injector */
     560             :   public Module createSshModule() {
     561         132 :     return null;
     562             :   }
     563             : 
     564             :   protected void initSsh() throws Exception {
     565         132 :     if (testRequiresSsh
     566          13 :         && SshMode.useSsh()
     567             :         && (adminSshSession == null || userSshSession == null)) {
     568             :       // Create Ssh sessions
     569          13 :       SshSessionFactory.initSsh();
     570          13 :       Context ctx = newRequestContext(user);
     571          13 :       atrScope.set(ctx);
     572          13 :       userSshSession = ctx.getSession();
     573          13 :       userSshSession.open();
     574          13 :       ctx = newRequestContext(admin);
     575          13 :       atrScope.set(ctx);
     576          13 :       adminSshSession = ctx.getSession();
     577          13 :       adminSshSession.open();
     578             :     }
     579         132 :   }
     580             : 
     581             :   private TestAccount getCloneAsAccount(Description description) {
     582         129 :     TestProjectInput ann = description.getAnnotation(TestProjectInput.class);
     583         129 :     return accountCreator.get(ann != null ? ann.cloneAs() : "admin");
     584             :   }
     585             : 
     586             :   /** Generate default project properties based on test description */
     587             :   private ProjectInput projectInput(Description description) {
     588         132 :     ProjectInput in = new ProjectInput();
     589         132 :     TestProjectInput ann = description.getAnnotation(TestProjectInput.class);
     590         132 :     in.name = name("project");
     591         132 :     in.branches = ImmutableList.of(Constants.R_HEADS + Constants.MASTER);
     592         132 :     if (ann != null) {
     593          18 :       in.parent = Strings.emptyToNull(ann.parent());
     594          18 :       in.description = Strings.emptyToNull(ann.description());
     595          18 :       in.createEmptyCommit = ann.createEmptyCommit();
     596          18 :       in.submitType = ann.submitType();
     597          18 :       in.useContentMerge = ann.useContributorAgreements();
     598          18 :       in.useSignedOffBy = ann.useSignedOffBy();
     599          18 :       in.useContentMerge = ann.useContentMerge();
     600          18 :       in.rejectEmptyCommit = ann.rejectEmptyCommit();
     601          18 :       in.enableSignedPush = ann.enableSignedPush();
     602          18 :       in.requireSignedPush = ann.requireSignedPush();
     603             :     } else {
     604             :       // Defaults should match TestProjectConfig, omitting nullable values.
     605         132 :       in.createEmptyCommit = true;
     606             :     }
     607         132 :     updateProjectInput(in);
     608         132 :     return in;
     609             :   }
     610             : 
     611             :   /**
     612             :    * Modify a project input before creating the initial test project.
     613             :    *
     614             :    * @param in input; may be modified in place.
     615             :    */
     616             :   protected void updateProjectInput(ProjectInput in) {
     617             :     // Default implementation does nothing.
     618         126 :   }
     619             : 
     620         132 :   private static final Pattern UNSAFE_PROJECT_NAME = Pattern.compile("[^a-zA-Z0-9._/-]+");
     621             : 
     622             :   protected Git git() {
     623           5 :     return testRepo.git();
     624             :   }
     625             : 
     626             :   protected InMemoryRepository repo() {
     627          23 :     return testRepo.getRepository();
     628             :   }
     629             : 
     630             :   /**
     631             :    * Return a resource name scoped to this test method.
     632             :    *
     633             :    * <p>Test methods in a single class by default share a running server. For any resource name you
     634             :    * require to be unique to a test method, wrap it in a call to this method.
     635             :    *
     636             :    * @param name resource name (group, project, topic, etc.)
     637             :    * @return name prefixed by a string unique to this test method.
     638             :    */
     639             :   protected String name(String name) {
     640         132 :     return resourcePrefix + name;
     641             :   }
     642             : 
     643             :   protected Project.NameKey createProjectOverAPI(
     644             :       String nameSuffix,
     645             :       @Nullable Project.NameKey parent,
     646             :       boolean createEmptyCommit,
     647             :       @Nullable SubmitType submitType)
     648             :       throws RestApiException {
     649           4 :     ProjectInput in = new ProjectInput();
     650           4 :     in.name = name(nameSuffix);
     651           4 :     in.parent = parent != null ? parent.get() : null;
     652           4 :     in.submitType = submitType;
     653           4 :     in.createEmptyCommit = createEmptyCommit;
     654           4 :     gApi.projects().create(in);
     655           4 :     return Project.nameKey(in.name);
     656             :   }
     657             : 
     658             :   protected TestRepository<InMemoryRepository> cloneProject(Project.NameKey p) throws Exception {
     659          22 :     return cloneProject(p, admin);
     660             :   }
     661             : 
     662             :   protected TestRepository<InMemoryRepository> cloneProject(
     663             :       Project.NameKey p, TestAccount testAccount) throws Exception {
     664         132 :     return GitUtil.cloneProject(p, registerRepoConnection(p, testAccount));
     665             :   }
     666             : 
     667             :   /**
     668             :    * Register a repository connection over the test protocol.
     669             :    *
     670             :    * @return a URI string that can be used to connect to this repository for both fetch and push.
     671             :    */
     672             :   protected String registerRepoConnection(Project.NameKey p, TestAccount testAccount)
     673             :       throws Exception {
     674         132 :     InProcessProtocol.Context ctx =
     675         132 :         new InProcessProtocol.Context(identifiedUserFactory, testAccount.id(), p);
     676         132 :     Repository repo = repoManager.openRepository(p);
     677         132 :     toClose.add(repo);
     678         132 :     return inProcessProtocol.register(ctx, repo).toString();
     679             :   }
     680             : 
     681             :   protected void afterTest() throws Exception {
     682         132 :     Transport.unregister(inProcessProtocol);
     683         132 :     for (Repository repo : toClose) {
     684         132 :       repo.close();
     685         132 :     }
     686         132 :     closeSsh();
     687         132 :     resetTimeSettings();
     688         132 :     if (server != commonServer) {
     689          53 :       server.close();
     690          53 :       server = null;
     691             :     }
     692         132 :     SystemReader.setInstance(oldSystemReader);
     693         132 :     oldSystemReader = null;
     694             :     // Set useDefaultTicker in afterTest, so the next beforeTest will use the default ticker
     695         132 :     testTicker.useDefaultTicker();
     696         132 :   }
     697             : 
     698             :   protected void closeSsh() {
     699         132 :     if (adminSshSession != null) {
     700          13 :       adminSshSession.close();
     701          13 :       adminSshSession = null;
     702             :     }
     703         132 :     if (userSshSession != null) {
     704          13 :       userSshSession.close();
     705          13 :       userSshSession = null;
     706             :     }
     707         132 :   }
     708             : 
     709             :   /**
     710             :    * Verify that NoteDB commits do not persist user-sensitive information, by running checks for all
     711             :    * commits in {@link RefNames#changeMetaRef} for all changes, created during the test.
     712             :    *
     713             :    * <p>These tests prevent regression, assuming appropriate test coverage for new features. The
     714             :    * verification is disabled by default and can be enabled using {@link VerifyNoPiiInChangeNotes}
     715             :    * annotation either on test class or method.
     716             :    */
     717             :   protected void verifyNoAccountDetailsInChangeNotes() throws RestApiException, IOException {
     718           4 :     List<ChangeInfo> allChanges = gApi.changes().query().get();
     719             : 
     720           4 :     List<AccountState> allAccounts = accounts.all();
     721           4 :     for (ChangeInfo change : allChanges) {
     722           4 :       try (Repository repo = repoManager.openRepository(Project.nameKey(change.project))) {
     723           4 :         String metaRefName =
     724           4 :             RefNames.changeMetaRef(Change.Id.tryParse(change._number.toString()).get());
     725           4 :         ObjectId metaTip = repo.getRefDatabase().exactRef(metaRefName).getObjectId();
     726           4 :         ChangeNotesRevWalk revWalk = ChangeNotesCommit.newRevWalk(repo);
     727           4 :         revWalk.reset();
     728           4 :         revWalk.markStart(revWalk.parseCommit(metaTip));
     729             :         ChangeNotesCommit commit;
     730           4 :         while ((commit = revWalk.next()) != null) {
     731           4 :           String fullMessage = commit.getFullMessage();
     732           4 :           for (AccountState accountState : allAccounts) {
     733           4 :             Account account = accountState.account();
     734           4 :             assertThat(fullMessage).doesNotContain(account.getName());
     735           4 :             if (account.fullName() != null) {
     736           4 :               assertThat(fullMessage).doesNotContain(account.fullName());
     737             :             }
     738           4 :             if (account.displayName() != null) {
     739           4 :               assertThat(fullMessage).doesNotContain(account.displayName());
     740             :             }
     741           4 :             if (account.preferredEmail() != null) {
     742           4 :               assertThat(fullMessage).doesNotContain(account.preferredEmail());
     743             :             }
     744           4 :             if (accountState.userName().isPresent()) {
     745           4 :               assertThat(fullMessage).doesNotContain(accountState.userName().get());
     746             :             }
     747           4 :             List<String> allEmails =
     748           4 :                 accountState.externalIds().stream()
     749           4 :                     .map(ExternalId::email)
     750           4 :                     .filter(Objects::nonNull)
     751           4 :                     .collect(toImmutableList());
     752           4 :             for (String email : allEmails) {
     753           4 :               assertThat(fullMessage).doesNotContain(email);
     754           4 :             }
     755           4 :           }
     756           4 :         }
     757             :       }
     758           4 :     }
     759           4 :   }
     760             : 
     761             :   protected TestRepository<?>.CommitBuilder commitBuilder() throws Exception {
     762          16 :     return testRepo.branch("HEAD").commit().insertChangeId();
     763             :   }
     764             : 
     765             :   protected TestRepository<?>.CommitBuilder amendBuilder() throws Exception {
     766           4 :     ObjectId head = repo().exactRef("HEAD").getObjectId();
     767           4 :     TestRepository<?>.CommitBuilder b = testRepo.amendRef("HEAD");
     768           4 :     Optional<String> id = GitUtil.getChangeId(testRepo, head);
     769             :     // TestRepository behaves like "git commit --amend -m foo", which does not
     770             :     // preserve an existing Change-Id. Tests probably want this.
     771           4 :     if (id.isPresent()) {
     772           4 :       b.insertChangeId(id.get().substring(1));
     773             :     } else {
     774           0 :       b.insertChangeId();
     775             :     }
     776           4 :     return b;
     777             :   }
     778             : 
     779             :   protected PushOneCommit.Result createChange() throws Exception {
     780          61 :     return createChange("refs/for/master");
     781             :   }
     782             : 
     783             :   protected PushOneCommit.Result createChange(String ref) throws Exception {
     784          61 :     PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
     785          61 :     PushOneCommit.Result result = push.to(ref);
     786          61 :     result.assertOkStatus();
     787          61 :     return result;
     788             :   }
     789             : 
     790             :   protected PushOneCommit.Result createChange(TestRepository<InMemoryRepository> repo)
     791             :       throws Exception {
     792           3 :     PushOneCommit push = pushFactory.create(admin.newIdent(), repo);
     793           3 :     PushOneCommit.Result result = push.to("refs/for/master");
     794           3 :     result.assertOkStatus();
     795           3 :     return result;
     796             :   }
     797             : 
     798             :   protected PushOneCommit.Result createMergeCommitChange(String ref) throws Exception {
     799           5 :     return createMergeCommitChange(ref, "foo");
     800             :   }
     801             : 
     802             :   protected PushOneCommit.Result createMergeCommitChange(String ref, String file) throws Exception {
     803           6 :     ObjectId initial = repo().exactRef(HEAD).getLeaf().getObjectId();
     804             : 
     805           6 :     PushOneCommit.Result p1 =
     806             :         pushFactory
     807           6 :             .create(
     808           6 :                 admin.newIdent(),
     809             :                 testRepo,
     810             :                 "parent 1",
     811           6 :                 ImmutableMap.of(file, "foo-1", "bar", "bar-1"))
     812           6 :             .to(ref);
     813             : 
     814             :     // reset HEAD in order to create a sibling of the first change
     815           6 :     testRepo.reset(initial);
     816             : 
     817           6 :     PushOneCommit.Result p2 =
     818             :         pushFactory
     819           6 :             .create(
     820           6 :                 admin.newIdent(),
     821             :                 testRepo,
     822             :                 "parent 2",
     823           6 :                 ImmutableMap.of(file, "foo-2", "bar", "bar-2"))
     824           6 :             .to(ref);
     825             : 
     826           6 :     PushOneCommit m =
     827           6 :         pushFactory.create(
     828           6 :             admin.newIdent(), testRepo, "merge", ImmutableMap.of(file, "foo-1", "bar", "bar-2"));
     829           6 :     m.setParents(ImmutableList.of(p1.getCommit(), p2.getCommit()));
     830           6 :     PushOneCommit.Result result = m.to(ref);
     831           6 :     result.assertOkStatus();
     832           6 :     return result;
     833             :   }
     834             : 
     835             :   protected PushOneCommit.Result createNParentsMergeCommitChange(String ref, List<String> fileNames)
     836             :       throws Exception {
     837             :     // This method creates n different commits and creates a merge commit pointing to all n parents.
     838             :     // Each commit will contain all the fileNames. Commit i will have the following file names and
     839             :     // their contents:
     840             :     // {$file_1_name, ${file_1_name}-1}
     841             :     // {$file_2_name, ${file_2_name}-1}, etc...
     842             :     // The merge commit will have:
     843             :     // {$file_1_name, ${file_1_name}-1}
     844             :     // {$file_2_name, ${file_2_name}-2},
     845             :     // {$file_3_name, ${file_3_name}-3}, etc...
     846             :     // i.e. taking the ith file from the ith commit.
     847           2 :     int n = fileNames.size();
     848           2 :     ObjectId initial = repo().exactRef(HEAD).getLeaf().getObjectId();
     849             : 
     850           2 :     List<PushOneCommit.Result> pushResults = new ArrayList<>();
     851             : 
     852           2 :     for (int i = 1; i <= n; i++) {
     853           2 :       int finalI = i;
     854           2 :       pushResults.add(
     855             :           pushFactory
     856           2 :               .create(
     857           2 :                   admin.newIdent(),
     858             :                   testRepo,
     859             :                   "parent " + i,
     860           2 :                   fileNames.stream().collect(Collectors.toMap(f -> f, f -> f + "-" + finalI)))
     861           2 :               .to(ref));
     862             : 
     863             :       // reset HEAD in order to create a sibling of the first change
     864           2 :       if (i < n) {
     865           2 :         testRepo.reset(initial);
     866             :       }
     867             :     }
     868             : 
     869           2 :     PushOneCommit m =
     870           2 :         pushFactory.create(
     871           2 :             admin.newIdent(),
     872             :             testRepo,
     873             :             "merge",
     874           2 :             IntStream.range(1, n + 1)
     875           2 :                 .boxed()
     876           2 :                 .collect(
     877           2 :                     Collectors.toMap(
     878           2 :                         i -> fileNames.get(i - 1), i -> fileNames.get(i - 1) + "-" + i)));
     879             : 
     880           2 :     m.setParents(pushResults.stream().map(PushOneCommit.Result::getCommit).collect(toList()));
     881           2 :     PushOneCommit.Result result = m.to(ref);
     882           2 :     result.assertOkStatus();
     883           2 :     return result;
     884             :   }
     885             : 
     886             :   protected PushOneCommit.Result createCommitAndPush(
     887             :       TestRepository<InMemoryRepository> repo,
     888             :       String ref,
     889             :       String commitMsg,
     890             :       String fileName,
     891             :       String content)
     892             :       throws Exception {
     893           2 :     PushOneCommit.Result result =
     894           2 :         pushFactory.create(admin.newIdent(), repo, commitMsg, fileName, content).to(ref);
     895           2 :     result.assertOkStatus();
     896           2 :     return result;
     897             :   }
     898             : 
     899             :   protected PushOneCommit.Result createChangeWithTopic(
     900             :       TestRepository<InMemoryRepository> repo,
     901             :       String topic,
     902             :       String commitMsg,
     903             :       String fileName,
     904             :       String content)
     905             :       throws Exception {
     906           1 :     assertThat(topic).isNotEmpty();
     907           1 :     return createCommitAndPush(
     908           1 :         repo, "refs/for/master%topic=" + name(topic), commitMsg, fileName, content);
     909             :   }
     910             : 
     911             :   protected PushOneCommit.Result createChange(String subject, String fileName, String content)
     912             :       throws Exception {
     913          23 :     PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo, subject, fileName, content);
     914          23 :     return push.to("refs/for/master");
     915             :   }
     916             : 
     917             :   protected PushOneCommit.Result createChange(
     918             :       TestRepository<?> repo,
     919             :       String branch,
     920             :       String subject,
     921             :       String fileName,
     922             :       String content,
     923             :       String topic)
     924             :       throws Exception {
     925          16 :     PushOneCommit push = pushFactory.create(admin.newIdent(), repo, subject, fileName, content);
     926          16 :     return push.to("refs/for/" + branch + "%topic=" + name(topic));
     927             :   }
     928             : 
     929             :   protected BranchApi createBranch(BranchNameKey branch) throws Exception {
     930          13 :     return gApi.projects()
     931          13 :         .name(branch.project().get())
     932          13 :         .branch(branch.branch())
     933          13 :         .create(new BranchInput());
     934             :   }
     935             : 
     936             :   protected BranchApi createBranchWithRevision(BranchNameKey branch, String revision)
     937             :       throws Exception {
     938           6 :     BranchInput in = new BranchInput();
     939           6 :     in.revision = revision;
     940           6 :     return gApi.projects().name(branch.project().get()).branch(branch.branch()).create(in);
     941             :   }
     942             : 
     943         132 :   private static final List<Character> RANDOM =
     944         132 :       Chars.asList('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h');
     945             : 
     946             :   protected PushOneCommit.Result amendChangeWithUploader(
     947             :       PushOneCommit.Result change, Project.NameKey projectName, TestAccount account)
     948             :       throws Exception {
     949           4 :     TestRepository<InMemoryRepository> repo = cloneProject(projectName, account);
     950           4 :     GitUtil.fetch(repo, "refs/*:refs/*");
     951           4 :     repo.reset(change.getCommit());
     952           4 :     PushOneCommit.Result result =
     953           4 :         amendChange(
     954           4 :             change.getChangeId(),
     955             :             "refs/for/master",
     956             :             account,
     957             :             repo,
     958             :             "new subject",
     959             :             "new file",
     960           4 :             "new content " + UUID.randomUUID());
     961           4 :     return result;
     962             :   }
     963             : 
     964             :   protected PushOneCommit.Result amendChange(String changeId) throws Exception {
     965          28 :     return amendChange(changeId, "refs/for/master", admin, testRepo);
     966             :   }
     967             : 
     968             :   protected PushOneCommit.Result amendChange(
     969             :       String changeId, String ref, TestAccount testAccount, TestRepository<?> repo)
     970             :       throws Exception {
     971          31 :     Collections.shuffle(RANDOM);
     972          31 :     return amendChange(
     973             :         changeId,
     974             :         ref,
     975             :         testAccount,
     976             :         repo,
     977             :         PushOneCommit.SUBJECT,
     978             :         PushOneCommit.FILE_NAME,
     979          31 :         new String(Chars.toArray(RANDOM)));
     980             :   }
     981             : 
     982             :   protected PushOneCommit.Result amendChange(
     983             :       String changeId, String subject, String fileName, String content) throws Exception {
     984           7 :     return amendChange(changeId, "refs/for/master", admin, testRepo, subject, fileName, content);
     985             :   }
     986             : 
     987             :   protected PushOneCommit.Result amendChange(
     988             :       String changeId,
     989             :       String ref,
     990             :       TestAccount testAccount,
     991             :       TestRepository<?> repo,
     992             :       String subject,
     993             :       String fileName,
     994             :       String content)
     995             :       throws Exception {
     996          33 :     PushOneCommit push =
     997          33 :         pushFactory.create(testAccount.newIdent(), repo, subject, fileName, content, changeId);
     998          33 :     return push.to(ref);
     999             :   }
    1000             : 
    1001             :   protected void merge(PushOneCommit.Result r) throws Exception {
    1002           7 :     revision(r).review(ReviewInput.approve());
    1003           7 :     revision(r).submit();
    1004           7 :   }
    1005             : 
    1006             :   protected ChangeInfo info(String id) throws RestApiException {
    1007          17 :     return gApi.changes().id(id).info();
    1008             :   }
    1009             : 
    1010             :   protected ChangeApi change(Result r) throws RestApiException {
    1011          15 :     return gApi.changes().id(r.getChange().getId().get());
    1012             :   }
    1013             : 
    1014             :   protected Optional<EditInfo> getEdit(String id) throws RestApiException {
    1015           5 :     return gApi.changes().id(id).edit().get();
    1016             :   }
    1017             : 
    1018             :   protected ChangeInfo get(String id, ListChangesOption... options) throws RestApiException {
    1019          18 :     return gApi.changes().id(id).get(options);
    1020             :   }
    1021             : 
    1022             :   protected AccountInfo getAccountInfo(Account.Id accountId) throws RestApiException {
    1023           2 :     return gApi.accounts().id(accountId.get()).get();
    1024             :   }
    1025             : 
    1026             :   protected List<ChangeInfo> query(String q) throws RestApiException {
    1027           9 :     return gApi.changes().query(q).get();
    1028             :   }
    1029             : 
    1030             :   private Context newRequestContext(TestAccount account) {
    1031         132 :     requestScopeOperations.setApiUser(account.id());
    1032         132 :     return atrScope.get();
    1033             :   }
    1034             : 
    1035             :   protected Account getAccount(Account.Id accountId) {
    1036           5 :     return getAccountState(accountId).account();
    1037             :   }
    1038             : 
    1039             :   protected AccountState getAccountState(Account.Id accountId) {
    1040           5 :     Optional<AccountState> accountState = accountCache.get(accountId);
    1041           5 :     assertWithMessage("account %s", accountId.get())
    1042           5 :         .about(optionals())
    1043           5 :         .that(accountState)
    1044           5 :         .isPresent();
    1045           5 :     return accountState.get();
    1046             :   }
    1047             : 
    1048             :   protected AutoCloseable disableNoteDb() {
    1049           6 :     changeNotesArgs.failOnLoadForTest.set(true);
    1050           6 :     Context oldContext = atrScope.disableNoteDb();
    1051           6 :     return () -> {
    1052           6 :       changeNotesArgs.failOnLoadForTest.set(false);
    1053           6 :       atrScope.set(oldContext);
    1054           6 :     };
    1055             :   }
    1056             : 
    1057             :   protected static Gson newGson() {
    1058          15 :     return OutputFormat.JSON_COMPACT.newGson();
    1059             :   }
    1060             : 
    1061             :   protected RevisionApi revision(PushOneCommit.Result r) throws Exception {
    1062          18 :     return gApi.changes().id(r.getChangeId()).current();
    1063             :   }
    1064             : 
    1065             :   protected void setUseSignedOffBy(InheritableBoolean value) throws Exception {
    1066           3 :     try (MetaDataUpdate md = metaDataUpdateFactory.create(project)) {
    1067           3 :       ProjectConfig config = projectConfigFactory.read(md);
    1068           3 :       config.updateProject(p -> p.setBooleanConfig(BooleanProjectConfig.USE_SIGNED_OFF_BY, value));
    1069           3 :       config.commit(md);
    1070           3 :       projectCache.evictAndReindex(config.getProject());
    1071             :     }
    1072           3 :   }
    1073             : 
    1074             :   protected void setRequireChangeId(InheritableBoolean value) throws Exception {
    1075           4 :     try (MetaDataUpdate md = metaDataUpdateFactory.create(project)) {
    1076           4 :       ProjectConfig config = projectConfigFactory.read(md);
    1077           4 :       config.updateProject(p -> p.setBooleanConfig(BooleanProjectConfig.REQUIRE_CHANGE_ID, value));
    1078           4 :       config.commit(md);
    1079           4 :       projectCache.evictAndReindex(config.getProject());
    1080             :     }
    1081           4 :   }
    1082             : 
    1083             :   protected void blockAnonymousRead() throws Exception {
    1084           7 :     String allRefs = RefNames.REFS + "*";
    1085           7 :     projectOperations
    1086           7 :         .project(project)
    1087           7 :         .forUpdate()
    1088           7 :         .add(block(Permission.READ).ref(allRefs).group(ANONYMOUS_USERS))
    1089           7 :         .add(allow(Permission.READ).ref(allRefs).group(REGISTERED_USERS))
    1090           7 :         .update();
    1091           7 :   }
    1092             : 
    1093             :   protected PushOneCommit.Result pushTo(String ref) throws Exception {
    1094          18 :     PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
    1095          18 :     return push.to(ref);
    1096             :   }
    1097             : 
    1098             :   protected void approve(String id) throws Exception {
    1099          26 :     gApi.changes().id(id).current().review(ReviewInput.approve());
    1100          26 :   }
    1101             : 
    1102             :   protected void recommend(String id) throws Exception {
    1103           5 :     gApi.changes().id(id).current().review(ReviewInput.recommend());
    1104           5 :   }
    1105             : 
    1106             :   protected void assertThatAccountIsNotVisible(TestAccount... testAccounts) {
    1107           5 :     for (TestAccount testAccount : testAccounts) {
    1108           5 :       assertThrows(
    1109           0 :           ResourceNotFoundException.class, () -> gApi.accounts().id(testAccount.id().get()).get());
    1110             :     }
    1111           5 :   }
    1112             : 
    1113             :   protected void assertReviewers(String changeId, TestAccount... expectedReviewers)
    1114             :       throws RestApiException {
    1115           2 :     Map<ReviewerState, Collection<AccountInfo>> reviewerMap =
    1116           2 :         gApi.changes().id(changeId).get().reviewers;
    1117           2 :     assertThat(reviewerMap).containsKey(ReviewerState.REVIEWER);
    1118           2 :     List<Integer> reviewers =
    1119           2 :         reviewerMap.get(ReviewerState.REVIEWER).stream().map(a -> a._accountId).collect(toList());
    1120           2 :     assertThat(reviewers)
    1121           2 :         .containsExactlyElementsIn(
    1122           2 :             Arrays.stream(expectedReviewers).map(a -> a.id().get()).collect(toList()));
    1123           2 :   }
    1124             : 
    1125             :   protected void assertCcs(String changeId, TestAccount... expectedCcs) throws RestApiException {
    1126           2 :     Map<ReviewerState, Collection<AccountInfo>> reviewerMap =
    1127           2 :         gApi.changes().id(changeId).get().reviewers;
    1128           2 :     assertThat(reviewerMap).containsKey(ReviewerState.CC);
    1129           2 :     List<Integer> ccs =
    1130           2 :         reviewerMap.get(ReviewerState.CC).stream().map(a -> a._accountId).collect(toList());
    1131           2 :     assertThat(ccs)
    1132           2 :         .containsExactlyElementsIn(
    1133           2 :             Arrays.stream(expectedCcs).map(a -> a.id().get()).collect(toList()));
    1134           2 :   }
    1135             : 
    1136             :   protected void assertNoCcs(String changeId) throws RestApiException {
    1137           1 :     Map<ReviewerState, Collection<AccountInfo>> reviewerMap =
    1138           1 :         gApi.changes().id(changeId).get().reviewers;
    1139           1 :     assertThat(reviewerMap).doesNotContainKey(ReviewerState.CC);
    1140           1 :   }
    1141             : 
    1142             :   protected void assertSubmittedTogether(String chId, String... expected) throws Exception {
    1143           2 :     assertSubmittedTogether(chId, ImmutableSet.of(), expected);
    1144           2 :   }
    1145             : 
    1146             :   protected void assertSubmittedTogetherWithTopicClosure(String chId, String... expected)
    1147             :       throws Exception {
    1148           1 :     assertSubmittedTogether(chId, ImmutableSet.of(TOPIC_CLOSURE), expected);
    1149           1 :   }
    1150             : 
    1151             :   protected void assertSubmittedTogether(
    1152             :       String chId,
    1153             :       ImmutableSet<SubmittedTogetherOption> submittedTogetherOptions,
    1154             :       String... expected)
    1155             :       throws Exception {
    1156             :     // This does not include NON_VISIBILE_CHANGES
    1157             :     List<ChangeInfo> actual =
    1158           2 :         submittedTogetherOptions.isEmpty()
    1159           2 :             ? gApi.changes().id(chId).submittedTogether()
    1160           1 :             : gApi.changes()
    1161           1 :                 .id(chId)
    1162           2 :                 .submittedTogether(EnumSet.copyOf(submittedTogetherOptions))
    1163           1 :                 .changes;
    1164             : 
    1165             :     EnumSet<SubmittedTogetherOption> enumSetIncludingNonVisibleChanges =
    1166           2 :         submittedTogetherOptions.isEmpty()
    1167           2 :             ? EnumSet.of(NON_VISIBLE_CHANGES)
    1168           2 :             : EnumSet.copyOf(submittedTogetherOptions);
    1169           2 :     enumSetIncludingNonVisibleChanges.add(NON_VISIBLE_CHANGES);
    1170             : 
    1171             :     // This includes NON_VISIBLE_CHANGES for comparison.
    1172           2 :     SubmittedTogetherInfo info =
    1173           2 :         gApi.changes().id(chId).submittedTogether(enumSetIncludingNonVisibleChanges);
    1174             : 
    1175           2 :     assertThat(info.nonVisibleChanges).isEqualTo(0);
    1176           2 :     assertThat(Iterables.transform(actual, i1 -> i1.changeId))
    1177           2 :         .containsExactly((Object[]) expected)
    1178           2 :         .inOrder();
    1179           2 :     assertThat(Iterables.transform(info.changes, i -> i.changeId))
    1180           2 :         .containsExactly((Object[]) expected)
    1181           2 :         .inOrder();
    1182           2 :   }
    1183             : 
    1184             :   protected PatchSet getPatchSet(PatchSet.Id psId) {
    1185           0 :     return changeDataFactory.create(project, psId.changeId()).patchSet(psId);
    1186             :   }
    1187             : 
    1188             :   protected IdentifiedUser user(TestAccount testAccount) {
    1189           4 :     return identifiedUserFactory.create(testAccount.id());
    1190             :   }
    1191             : 
    1192             :   protected RevisionResource parseCurrentRevisionResource(String changeId) throws Exception {
    1193           6 :     ChangeResource cr = parseChangeResource(changeId);
    1194           6 :     int psId = cr.getChange().currentPatchSetId().get();
    1195           6 :     return revisions.parse(cr, IdString.fromDecoded(Integer.toString(psId)));
    1196             :   }
    1197             : 
    1198             :   protected RevisionResource parseRevisionResource(String changeId, int n) throws Exception {
    1199           0 :     return revisions.parse(
    1200           0 :         parseChangeResource(changeId), IdString.fromDecoded(Integer.toString(n)));
    1201             :   }
    1202             : 
    1203             :   protected RevisionResource parseRevisionResource(PushOneCommit.Result r) throws Exception {
    1204           0 :     PatchSet.Id psId = r.getPatchSetId();
    1205           0 :     return parseRevisionResource(psId.changeId().toString(), psId.get());
    1206             :   }
    1207             : 
    1208             :   protected ChangeResource parseChangeResource(String changeId) throws Exception {
    1209           8 :     List<ChangeNotes> notes = changeFinder.find(changeId);
    1210           8 :     assertThat(notes).hasSize(1);
    1211           8 :     return changeResourceFactory.create(notes.get(0), atrScope.get().getUser());
    1212             :   }
    1213             : 
    1214             :   protected RevCommit getHead(Repository repo, String name) throws Exception {
    1215          12 :     try (RevWalk rw = new RevWalk(repo)) {
    1216          12 :       Ref r = repo.exactRef(name);
    1217          12 :       return rw.parseCommit(r.getObjectId());
    1218             :     }
    1219             :   }
    1220             : 
    1221             :   protected void assertMailReplyTo(Message message, String email) throws Exception {
    1222           3 :     assertThat(message.headers()).containsKey("Reply-To");
    1223           3 :     StringEmailHeader replyTo = (StringEmailHeader) message.headers().get("Reply-To");
    1224           3 :     assertThat(replyTo.getString()).contains(email);
    1225           3 :   }
    1226             : 
    1227             :   protected void assertMailNotReplyTo(Message message, String email) throws Exception {
    1228           1 :     assertThat(message.headers()).containsKey("Reply-To");
    1229           1 :     StringEmailHeader replyTo = (StringEmailHeader) message.headers().get("Reply-To");
    1230           1 :     assertThat(replyTo.getString()).doesNotContain(email);
    1231           1 :   }
    1232             : 
    1233             :   /** Assert that the given branches have the given tree ids. */
    1234             :   protected void assertTrees(Project.NameKey proj, Map<BranchNameKey, ObjectId> trees)
    1235             :       throws Exception {
    1236           0 :     TestRepository<?> localRepo = cloneProject(proj);
    1237           0 :     GitUtil.fetch(localRepo, "refs/*:refs/*");
    1238           0 :     Map<BranchNameKey, RevTree> refValues = new HashMap<>();
    1239             : 
    1240           0 :     for (BranchNameKey b : trees.keySet()) {
    1241           0 :       if (!b.project().equals(proj)) {
    1242           0 :         continue;
    1243             :       }
    1244             : 
    1245           0 :       Ref r = localRepo.getRepository().exactRef(b.branch());
    1246           0 :       assertThat(r).isNotNull();
    1247           0 :       RevWalk rw = localRepo.getRevWalk();
    1248           0 :       RevCommit c = rw.parseCommit(r.getObjectId());
    1249           0 :       refValues.put(b, c.getTree());
    1250             : 
    1251           0 :       assertThat(trees.get(b)).isEqualTo(refValues.get(b));
    1252           0 :     }
    1253           0 :     assertThat(refValues.keySet()).containsAnyIn(trees.keySet());
    1254           0 :   }
    1255             : 
    1256             :   protected void assertDiffForFullyModifiedFile(
    1257             :       DiffInfo diff,
    1258             :       String commitName,
    1259             :       String path,
    1260             :       String expectedContentSideA,
    1261             :       String expectedContentSideB)
    1262             :       throws Exception {
    1263           1 :     assertDiffForFile(diff, commitName, path);
    1264             : 
    1265           1 :     ImmutableList<String> expectedOldLines =
    1266           1 :         ImmutableList.copyOf(expectedContentSideA.split("\n", -1));
    1267           1 :     ImmutableList<String> expectedNewLines =
    1268           1 :         ImmutableList.copyOf(expectedContentSideB.split("\n", -1));
    1269             : 
    1270           1 :     assertThat(diff.changeType).isEqualTo(ChangeType.MODIFIED);
    1271             : 
    1272           1 :     assertThat(diff.metaA).isNotNull();
    1273           1 :     assertThat(diff.metaB).isNotNull();
    1274             : 
    1275           1 :     assertThat(diff.metaA.name).isEqualTo(path);
    1276           1 :     assertThat(diff.metaA.lines).isEqualTo(expectedOldLines.size());
    1277           1 :     assertThat(diff.metaB.name).isEqualTo(path);
    1278           1 :     assertThat(diff.metaB.lines).isEqualTo(expectedNewLines.size());
    1279             : 
    1280           1 :     DiffInfo.ContentEntry contentEntry = diff.content.get(0);
    1281           1 :     assertThat(contentEntry.a).containsExactlyElementsIn(expectedOldLines).inOrder();
    1282           1 :     assertThat(contentEntry.b).containsExactlyElementsIn(expectedNewLines).inOrder();
    1283           1 :     assertThat(contentEntry.ab).isNull();
    1284           1 :     assertThat(contentEntry.common).isNull();
    1285           1 :     assertThat(contentEntry.editA).isNull();
    1286           1 :     assertThat(contentEntry.editB).isNull();
    1287           1 :     assertThat(contentEntry.skip).isNull();
    1288           1 :   }
    1289             : 
    1290             :   protected void assertDiffForNewFile(
    1291             :       DiffInfo diff, @Nullable RevCommit commit, String path, String expectedContentSideB)
    1292             :       throws Exception {
    1293           2 :     assertDiffForNewFile(diff, commit.name(), path, expectedContentSideB);
    1294           2 :   }
    1295             : 
    1296             :   protected void assertDiffForNewFile(
    1297             :       DiffInfo diff, String commitName, String path, String expectedContentSideB) throws Exception {
    1298           4 :     assertDiffForFile(diff, commitName, path);
    1299             : 
    1300           4 :     ImmutableList<String> expectedLines =
    1301           4 :         ImmutableList.copyOf(expectedContentSideB.split("\n", -1));
    1302             : 
    1303           4 :     assertThat(diff.changeType).isEqualTo(ChangeType.ADDED);
    1304             : 
    1305           4 :     assertThat(diff.metaA).isNull();
    1306           4 :     assertThat(diff.metaB).isNotNull();
    1307             : 
    1308           4 :     assertThat(diff.metaB.name).isEqualTo(path);
    1309           4 :     assertThat(diff.metaB.lines).isEqualTo(expectedLines.size());
    1310             : 
    1311           4 :     DiffInfo.ContentEntry contentEntry = diff.content.get(0);
    1312           4 :     assertThat(contentEntry.b).containsExactlyElementsIn(expectedLines).inOrder();
    1313           4 :     assertThat(contentEntry.a).isNull();
    1314           4 :     assertThat(contentEntry.ab).isNull();
    1315           4 :     assertThat(contentEntry.common).isNull();
    1316           4 :     assertThat(contentEntry.editA).isNull();
    1317           4 :     assertThat(contentEntry.editB).isNull();
    1318           4 :     assertThat(contentEntry.skip).isNull();
    1319           4 :   }
    1320             : 
    1321             :   protected void assertDiffForDeletedFile(DiffInfo diff, String path, String expectedContentSideA)
    1322             :       throws Exception {
    1323           1 :     assertDiffHeaders(diff);
    1324             : 
    1325           1 :     ImmutableList<String> expectedOriginalLines =
    1326           1 :         ImmutableList.copyOf(expectedContentSideA.split("\n", -1));
    1327             : 
    1328           1 :     assertThat(diff.changeType).isEqualTo(ChangeType.DELETED);
    1329             : 
    1330           1 :     assertThat(diff.metaA).isNotNull();
    1331           1 :     assertThat(diff.metaB).isNull();
    1332             : 
    1333           1 :     assertThat(diff.metaA.name).isEqualTo(path);
    1334           1 :     assertThat(diff.metaA.lines).isEqualTo(expectedOriginalLines.size());
    1335             : 
    1336           1 :     DiffInfo.ContentEntry contentEntry = diff.content.get(0);
    1337           1 :     assertThat(contentEntry.a).containsExactlyElementsIn(expectedOriginalLines).inOrder();
    1338           1 :     assertThat(contentEntry.b).isNull();
    1339           1 :     assertThat(contentEntry.ab).isNull();
    1340           1 :     assertThat(contentEntry.common).isNull();
    1341           1 :     assertThat(contentEntry.editA).isNull();
    1342           1 :     assertThat(contentEntry.editB).isNull();
    1343           1 :     assertThat(contentEntry.skip).isNull();
    1344           1 :   }
    1345             : 
    1346             :   private void assertDiffForFile(DiffInfo diff, String commitName, String path) throws Exception {
    1347           4 :     assertDiffHeaders(diff);
    1348             : 
    1349           4 :     assertThat(diff.metaB.commitId).isEqualTo(commitName);
    1350             : 
    1351           4 :     String expectedContentType = "text/plain";
    1352           4 :     if (COMMIT_MSG.equals(path)) {
    1353           1 :       expectedContentType = FileContentUtil.TEXT_X_GERRIT_COMMIT_MESSAGE;
    1354           4 :     } else if (MERGE_LIST.equals(path)) {
    1355           1 :       expectedContentType = FileContentUtil.TEXT_X_GERRIT_MERGE_LIST;
    1356             :     }
    1357             : 
    1358           4 :     assertThat(diff.metaB.contentType).isEqualTo(expectedContentType);
    1359             : 
    1360           4 :     assertThat(diff.metaB.name).isEqualTo(path);
    1361           4 :     assertThat(diff.metaB.webLinks).isNull();
    1362           4 :   }
    1363             : 
    1364             :   private void assertDiffHeaders(DiffInfo diff) throws Exception {
    1365           4 :     assertThat(diff.binary).isNull();
    1366           4 :     assertThat(diff.diffHeader).isNotNull();
    1367           4 :     assertThat(diff.intralineStatus).isNull();
    1368           4 :     assertThat(diff.webLinks).isNull();
    1369           4 :     assertThat(diff.editWebLinks).isNull();
    1370           4 :   }
    1371             : 
    1372             :   protected void assertPermitted(ChangeInfo info, String label, Integer... expected) {
    1373           3 :     assertThat(info.permittedLabels).isNotNull();
    1374           3 :     Collection<String> strs = info.permittedLabels.get(label);
    1375           3 :     if (expected.length == 0) {
    1376           2 :       assertThat(strs).isNull();
    1377             :     } else {
    1378           3 :       assertThat(strs.stream().map(s -> Integer.valueOf(s.trim())).collect(toList()))
    1379           3 :           .containsExactlyElementsIn(Arrays.asList(expected));
    1380             :     }
    1381           3 :   }
    1382             : 
    1383             :   protected void assertPermissions(
    1384             :       Project.NameKey project,
    1385             :       GroupReference groupReference,
    1386             :       String ref,
    1387             :       boolean exclusive,
    1388             :       String... permissionNames) {
    1389           3 :     Optional<AccessSection> accessSection =
    1390             :         projectCache
    1391           3 :             .get(project)
    1392           3 :             .orElseThrow(illegalState(project))
    1393           3 :             .getConfig()
    1394           3 :             .getAccessSection(ref);
    1395           3 :     assertThat(accessSection).isPresent();
    1396           3 :     for (String permissionName : permissionNames) {
    1397           3 :       Permission permission = accessSection.get().getPermission(permissionName);
    1398           3 :       assertPermission(permission, permissionName, exclusive, null);
    1399           3 :       assertPermissionRule(
    1400           3 :           permission.getRule(groupReference), groupReference, Action.ALLOW, false, 0, 0);
    1401             :     }
    1402           3 :   }
    1403             : 
    1404             :   protected void assertPermission(
    1405             :       Permission permission,
    1406             :       String expectedName,
    1407             :       boolean expectedExclusive,
    1408             :       @Nullable String expectedLabelName) {
    1409           3 :     assertThat(permission).isNotNull();
    1410           3 :     assertThat(permission.getName()).isEqualTo(expectedName);
    1411           3 :     assertThat(permission.getExclusiveGroup()).isEqualTo(expectedExclusive);
    1412           3 :     assertThat(permission.getLabel()).isEqualTo(expectedLabelName);
    1413           3 :   }
    1414             : 
    1415             :   protected void assertPermissionRule(
    1416             :       PermissionRule rule,
    1417             :       GroupReference expectedGroupReference,
    1418             :       Action expectedAction,
    1419             :       boolean expectedForce,
    1420             :       int expectedMin,
    1421             :       int expectedMax) {
    1422           3 :     assertThat(rule.getGroup()).isEqualTo(expectedGroupReference);
    1423           3 :     assertThat(rule.getAction()).isEqualTo(expectedAction);
    1424           3 :     assertThat(rule.getForce()).isEqualTo(expectedForce);
    1425           3 :     assertThat(rule.getMin()).isEqualTo(expectedMin);
    1426           3 :     assertThat(rule.getMax()).isEqualTo(expectedMax);
    1427           3 :   }
    1428             : 
    1429             :   protected void assertHead(String projectName, String expectedRef) throws Exception {
    1430             :     // Assert gerrit's project head points to the correct branch
    1431           3 :     assertThat(getProjectBranches(projectName).get(Constants.HEAD).revision)
    1432           3 :         .isEqualTo(RefNames.shortName(expectedRef));
    1433             :     // Assert git head points to the correct branch
    1434           3 :     try (Repository repo = repoManager.openRepository(Project.nameKey(projectName))) {
    1435           3 :       assertThat(repo.exactRef(Constants.HEAD).getTarget().getName()).isEqualTo(expectedRef);
    1436             :     }
    1437           3 :   }
    1438             : 
    1439             :   protected InternalGroup group(AccountGroup.UUID groupUuid) {
    1440           1 :     InternalGroup group = groupCache.get(groupUuid).orElse(null);
    1441           1 :     assertWithMessage(groupUuid.get()).that(group).isNotNull();
    1442           1 :     return group;
    1443             :   }
    1444             : 
    1445             :   protected GroupReference groupRef(AccountGroup.UUID groupUuid) {
    1446           3 :     GroupDescription.Basic groupDescription = groupBackend.get(groupUuid);
    1447           3 :     return GroupReference.create(groupDescription.getGroupUUID(), groupDescription.getName());
    1448             :   }
    1449             : 
    1450             :   protected InternalGroup group(String groupName) {
    1451          32 :     InternalGroup group = groupCache.get(AccountGroup.nameKey(groupName)).orElse(null);
    1452          32 :     assertWithMessage(groupName).that(group).isNotNull();
    1453          32 :     return group;
    1454             :   }
    1455             : 
    1456             :   protected GroupReference groupRef(String groupName) {
    1457           1 :     InternalGroup group = groupCache.get(AccountGroup.nameKey(groupName)).orElse(null);
    1458           1 :     assertThat(group).isNotNull();
    1459           1 :     return GroupReference.create(group.getGroupUUID(), group.getName());
    1460             :   }
    1461             : 
    1462             :   protected AccountGroup.UUID groupUuid(String groupName) {
    1463          31 :     return group(groupName).getGroupUUID();
    1464             :   }
    1465             : 
    1466             :   protected InternalGroup adminGroup() {
    1467           2 :     return group("Administrators");
    1468             :   }
    1469             : 
    1470             :   protected GroupReference adminGroupRef() {
    1471           1 :     return groupRef("Administrators");
    1472             :   }
    1473             : 
    1474             :   protected AccountGroup.UUID adminGroupUuid() {
    1475          30 :     return groupUuid("Administrators");
    1476             :   }
    1477             : 
    1478             :   protected void assertGroupDoesNotExist(String groupName) {
    1479           1 :     InternalGroup group = groupCache.get(AccountGroup.nameKey(groupName)).orElse(null);
    1480           1 :     assertWithMessage(groupName).that(group).isNull();
    1481           1 :   }
    1482             : 
    1483             :   protected void assertNotifyTo(TestAccount expected) {
    1484           5 :     assertNotifyTo(expected.email(), expected.fullName());
    1485           5 :   }
    1486             : 
    1487             :   protected void assertNotifyTo(String expectedEmail, String expectedFullname) {
    1488           6 :     Address expectedAddress = Address.create(expectedFullname, expectedEmail);
    1489           6 :     assertThat(sender.getMessages()).hasSize(1);
    1490           6 :     Message m = sender.getMessages().get(0);
    1491           6 :     assertThat(m.rcpt()).containsExactly(expectedAddress);
    1492           6 :     assertThat(((EmailHeader.AddressList) m.headers().get("To")).getAddressList())
    1493           6 :         .containsExactly(expectedAddress);
    1494           6 :     assertThat(m.headers().get("Cc").isEmpty()).isTrue();
    1495           6 :   }
    1496             : 
    1497             :   protected void assertNotifyCc(TestAccount expected) {
    1498           4 :     assertNotifyCc(expected.getNameEmail());
    1499           4 :   }
    1500             : 
    1501             :   protected void assertNotifyCc(String expectedEmail, String expectedFullname) {
    1502           1 :     Address expectedAddress = Address.create(expectedFullname, expectedEmail);
    1503           1 :     assertNotifyCc(expectedAddress);
    1504           1 :   }
    1505             : 
    1506             :   protected void assertNotifyCc(Address expectedAddress) {
    1507           6 :     assertThat(sender.getMessages()).hasSize(1);
    1508           6 :     Message m = sender.getMessages().get(0);
    1509           6 :     assertThat(m.rcpt()).containsExactly(expectedAddress);
    1510           6 :     assertThat(m.headers().get("To").isEmpty()).isTrue();
    1511           6 :     assertThat(((EmailHeader.AddressList) m.headers().get("Cc")).getAddressList())
    1512           6 :         .containsExactly(expectedAddress);
    1513           6 :   }
    1514             : 
    1515             :   protected void assertNotifyBcc(TestAccount expected) {
    1516           3 :     assertThat(sender.getMessages()).hasSize(1);
    1517           3 :     Message m = sender.getMessages().get(0);
    1518           3 :     assertThat(m.rcpt()).containsExactly(expected.getNameEmail());
    1519           3 :     assertThat(m.headers().get("To").isEmpty()).isTrue();
    1520           3 :     assertThat(m.headers().get("Cc").isEmpty()).isTrue();
    1521           3 :   }
    1522             : 
    1523             :   protected void assertNotifyBcc(String expectedEmail, String expectedFullName) {
    1524           1 :     assertThat(sender.getMessages()).hasSize(1);
    1525           1 :     Message m = sender.getMessages().get(0);
    1526           1 :     assertThat(m.rcpt()).containsExactly(Address.create(expectedFullName, expectedEmail));
    1527           1 :     assertThat(m.headers().get("To").isEmpty()).isTrue();
    1528           1 :     assertThat(m.headers().get("Cc").isEmpty()).isTrue();
    1529           1 :   }
    1530             : 
    1531             :   protected interface ProjectWatchInfoConfiguration {
    1532             :     void configure(ProjectWatchInfo pwi);
    1533             :   }
    1534             : 
    1535             :   protected void watch(String project, ProjectWatchInfoConfiguration config)
    1536             :       throws RestApiException {
    1537           4 :     ProjectWatchInfo pwi = new ProjectWatchInfo();
    1538           4 :     pwi.project = project;
    1539           4 :     config.configure(pwi);
    1540           4 :     gApi.accounts().self().setWatchedProjects(ImmutableList.of(pwi));
    1541           4 :   }
    1542             : 
    1543             :   protected void watch(PushOneCommit.Result r, ProjectWatchInfoConfiguration config)
    1544             :       throws RestApiException {
    1545           0 :     watch(r.getChange().project().get(), config);
    1546           0 :   }
    1547             : 
    1548             :   protected void watch(String project, String filter) throws RestApiException {
    1549           3 :     watch(
    1550             :         project,
    1551             :         pwi -> {
    1552           3 :           pwi.filter = filter;
    1553           3 :           pwi.notifyAbandonedChanges = true;
    1554           3 :           pwi.notifyNewChanges = true;
    1555           3 :           pwi.notifyNewPatchSets = true;
    1556           3 :           pwi.notifyAllComments = true;
    1557           3 :         });
    1558           3 :   }
    1559             : 
    1560             :   protected void watch(String project) throws RestApiException {
    1561           3 :     watch(project, (String) null);
    1562           3 :   }
    1563             : 
    1564             :   protected void assertContent(PushOneCommit.Result pushResult, String path, String expectedContent)
    1565             :       throws Exception {
    1566           2 :     BinaryResult bin =
    1567           2 :         gApi.changes()
    1568           2 :             .id(pushResult.getChangeId())
    1569           2 :             .revision(pushResult.getCommit().name())
    1570           2 :             .file(path)
    1571           2 :             .content();
    1572           2 :     ByteArrayOutputStream os = new ByteArrayOutputStream();
    1573           2 :     bin.writeTo(os);
    1574           2 :     String res = new String(os.toByteArray(), UTF_8);
    1575           2 :     assertThat(res).isEqualTo(expectedContent);
    1576           2 :   }
    1577             : 
    1578             :   protected RevCommit createNewCommitWithoutChangeId(String branch, String file, String content)
    1579             :       throws Exception {
    1580           2 :     try (Repository repo = repoManager.openRepository(project);
    1581           2 :         RevWalk walk = new RevWalk(repo)) {
    1582           2 :       Ref ref = repo.exactRef(branch);
    1583           2 :       RevCommit tip = null;
    1584           2 :       if (ref != null) {
    1585           2 :         tip = walk.parseCommit(ref.getObjectId());
    1586             :       }
    1587           2 :       TestRepository<?> testSrcRepo = new TestRepository<>(repo);
    1588           2 :       TestRepository<?>.BranchBuilder builder = testSrcRepo.branch(branch);
    1589             :       RevCommit revCommit =
    1590           2 :           tip == null
    1591           1 :               ? builder.commit().message("commit 1").add(file, content).create()
    1592           2 :               : builder.commit().parent(tip).message("commit 1").add(file, content).create();
    1593           2 :       assertThat(GitUtil.getChangeId(testSrcRepo, revCommit)).isEmpty();
    1594           2 :       return revCommit;
    1595             :     }
    1596             :   }
    1597             : 
    1598             :   protected RevCommit parseCurrentRevision(RevWalk rw, String changeId) throws Exception {
    1599           7 :     return rw.parseCommit(
    1600           7 :         ObjectId.fromString(get(changeId, ListChangesOption.CURRENT_REVISION).currentRevision));
    1601             :   }
    1602             : 
    1603             :   protected void configSubmitRequirement(
    1604             :       Project.NameKey project, SubmitRequirement submitRequirement) throws Exception {
    1605           3 :     try (ProjectConfigUpdate u = updateProject(project)) {
    1606           3 :       u.getConfig().upsertSubmitRequirement(submitRequirement);
    1607           3 :       u.save();
    1608             :     }
    1609           3 :   }
    1610             : 
    1611             :   protected void clearSubmitRequirements(Project.NameKey project) throws Exception {
    1612           1 :     try (ProjectConfigUpdate u = updateProject(project)) {
    1613           1 :       u.getConfig().clearSubmitRequirements();
    1614           1 :       u.save();
    1615             :     }
    1616           1 :   }
    1617             : 
    1618             :   protected void configLabel(String label, LabelFunction func) throws Exception {
    1619           8 :     configLabel(label, func, ImmutableList.of());
    1620           8 :   }
    1621             : 
    1622             :   protected void configLabel(String label, LabelFunction func, List<String> refPatterns)
    1623             :       throws Exception {
    1624           8 :     configLabel(
    1625             :         project,
    1626             :         label,
    1627             :         func,
    1628             :         refPatterns,
    1629           8 :         value(1, "Passes"),
    1630           8 :         value(0, "No score"),
    1631           8 :         value(-1, "Failed"));
    1632           8 :   }
    1633             : 
    1634             :   protected void configLabel(
    1635             :       Project.NameKey project, String label, LabelFunction func, LabelValue... value)
    1636             :       throws Exception {
    1637           3 :     configLabel(project, label, func, ImmutableList.of(), value);
    1638           3 :   }
    1639             : 
    1640             :   private void configLabel(
    1641             :       Project.NameKey project,
    1642             :       String label,
    1643             :       LabelFunction func,
    1644             :       List<String> refPatterns,
    1645             :       LabelValue... value)
    1646             :       throws Exception {
    1647           9 :     try (ProjectConfigUpdate u = updateProject(project)) {
    1648           9 :       LabelType.Builder labelType = label(label, value).toBuilder();
    1649           9 :       labelType.setFunction(func);
    1650           9 :       labelType.setRefPatterns(ImmutableList.copyOf(refPatterns));
    1651           9 :       u.getConfig().upsertLabelType(labelType.build());
    1652           9 :       u.save();
    1653             :     }
    1654           9 :   }
    1655             : 
    1656             :   protected void enableCreateNewChangeForAllNotInTarget() throws Exception {
    1657          11 :     try (ProjectConfigUpdate u = updateProject(project)) {
    1658          11 :       u.getConfig()
    1659          11 :           .updateProject(
    1660             :               p ->
    1661          11 :                   p.setBooleanConfig(
    1662             :                       BooleanProjectConfig.CREATE_NEW_CHANGE_FOR_ALL_NOT_IN_TARGET,
    1663             :                       InheritableBoolean.TRUE));
    1664          11 :       u.save();
    1665             :     }
    1666          11 :   }
    1667             : 
    1668             :   protected ProjectConfigUpdate updateProject(Project.NameKey projectName) throws Exception {
    1669          44 :     return new ProjectConfigUpdate(projectName);
    1670             :   }
    1671             : 
    1672             :   protected class ProjectConfigUpdate implements AutoCloseable {
    1673             :     private final ProjectConfig projectConfig;
    1674             :     private MetaDataUpdate metaDataUpdate;
    1675             : 
    1676          44 :     private ProjectConfigUpdate(Project.NameKey projectName) throws Exception {
    1677          44 :       metaDataUpdate = metaDataUpdateFactory.create(projectName);
    1678          44 :       projectConfig = projectConfigFactory.read(metaDataUpdate);
    1679          44 :     }
    1680             : 
    1681             :     public ProjectConfig getConfig() {
    1682          44 :       return projectConfig;
    1683             :     }
    1684             : 
    1685             :     public void save() throws Exception {
    1686          44 :       metaDataUpdate.setAuthor(identifiedUserFactory.create(admin.id()));
    1687          44 :       projectConfig.commit(metaDataUpdate);
    1688          44 :       metaDataUpdate.close();
    1689          44 :       metaDataUpdate = null;
    1690          44 :       projectCache.evictAndReindex(projectConfig.getProject());
    1691          44 :     }
    1692             : 
    1693             :     @Override
    1694             :     public void close() {
    1695          44 :       if (metaDataUpdate != null) {
    1696           0 :         metaDataUpdate.close();
    1697             :       }
    1698          44 :     }
    1699             :   }
    1700             : 
    1701             :   protected List<RevCommit> getChangeMetaCommitsInReverseOrder(Change.Id changeId)
    1702             :       throws IOException {
    1703           5 :     try (Repository repo = repoManager.openRepository(project);
    1704           5 :         RevWalk revWalk = new RevWalk(repo)) {
    1705           5 :       revWalk.sort(RevSort.TOPO);
    1706           5 :       revWalk.sort(RevSort.REVERSE);
    1707           5 :       Ref metaRef = repo.exactRef(RefNames.changeMetaRef(changeId));
    1708           5 :       revWalk.markStart(revWalk.parseCommit(metaRef.getObjectId()));
    1709           5 :       return Lists.newArrayList(revWalk);
    1710             :     }
    1711             :   }
    1712             : 
    1713             :   protected List<CommentInfo> getChangeSortedComments(int changeNum) throws Exception {
    1714           2 :     List<CommentInfo> comments = new ArrayList<>();
    1715           2 :     Map<String, List<CommentInfo>> commentsMap =
    1716           2 :         gApi.changes().id(changeNum).commentsRequest().get();
    1717           2 :     for (Map.Entry<String, List<CommentInfo>> e : commentsMap.entrySet()) {
    1718           2 :       for (CommentInfo c : e.getValue()) {
    1719           2 :         c.path = e.getKey(); // Set the comment's path field.
    1720           2 :         comments.add(c);
    1721           2 :       }
    1722           2 :     }
    1723           2 :     comments.sort(Comparator.comparing(c -> c.id));
    1724           2 :     return comments;
    1725             :   }
    1726             : 
    1727             :   protected ImmutableMap<String, BranchInfo> getProjectBranches(String projectName)
    1728             :       throws RestApiException {
    1729           3 :     return gApi.projects().name(projectName).branches().get().stream()
    1730           3 :         .collect(ImmutableMap.toImmutableMap(branch -> branch.ref, branch -> branch));
    1731             :   }
    1732             : 
    1733             :   protected AutoCloseable installPlugin(String pluginName, Class<? extends Module> sysModuleClass)
    1734             :       throws Exception {
    1735           7 :     return installPlugin(pluginName, sysModuleClass, null, null);
    1736             :   }
    1737             : 
    1738             :   protected AutoCloseable installPlugin(
    1739             :       String pluginName,
    1740             :       @Nullable Class<? extends Module> sysModuleClass,
    1741             :       @Nullable Class<? extends Module> httpModuleClass,
    1742             :       @Nullable Class<? extends Module> sshModuleClass)
    1743             :       throws Exception {
    1744           8 :     checkStatic(sysModuleClass);
    1745           8 :     checkStatic(httpModuleClass);
    1746           8 :     checkStatic(sshModuleClass);
    1747           8 :     TestServerPlugin plugin =
    1748             :         new TestServerPlugin(
    1749             :             pluginName,
    1750             :             "http://example.com/" + pluginName,
    1751           8 :             pluginUserFactory.create(pluginName),
    1752           8 :             getClass().getClassLoader(),
    1753           8 :             sysModuleClass != null ? sysModuleClass.getName() : null,
    1754           8 :             httpModuleClass != null ? httpModuleClass.getName() : null,
    1755           8 :             sshModuleClass != null ? sshModuleClass.getName() : null,
    1756           8 :             sitePaths.data_dir.resolve(pluginName));
    1757           8 :     plugin.start(pluginGuiceEnvironment);
    1758           8 :     pluginGuiceEnvironment.onStartPlugin(plugin);
    1759           8 :     return () -> {
    1760           8 :       plugin.stop(pluginGuiceEnvironment);
    1761           8 :       pluginGuiceEnvironment.onStopPlugin(plugin);
    1762           8 :     };
    1763             :   }
    1764             : 
    1765             :   private static void checkStatic(@Nullable Class<? extends Module> moduleClass) {
    1766           8 :     if (moduleClass != null) {
    1767           8 :       checkArgument(
    1768           8 :           (moduleClass.getModifiers() & Modifier.STATIC) != 0,
    1769             :           "module must be static: %s",
    1770           8 :           moduleClass.getName());
    1771             :     }
    1772           8 :   }
    1773             : 
    1774             :   /** {@link Ticker} implementation for mocking without restarting GerritServer */
    1775             :   public static class TestTicker extends Ticker {
    1776             :     Ticker actualTicker;
    1777             : 
    1778         138 :     public TestTicker() {
    1779         138 :       useDefaultTicker();
    1780         138 :     }
    1781             : 
    1782             :     /** Switches to system ticker */
    1783             :     public Ticker useDefaultTicker() {
    1784         138 :       this.actualTicker = Ticker.systemTicker();
    1785         138 :       return actualTicker;
    1786             :     }
    1787             : 
    1788             :     /** Switches to {@link FakeTicker} */
    1789             :     public FakeTicker useFakeTicker() {
    1790           1 :       if (!(this.actualTicker instanceof FakeTicker)) {
    1791           1 :         this.actualTicker = new FakeTicker();
    1792             :       }
    1793           1 :       return (FakeTicker) actualTicker;
    1794             :     }
    1795             : 
    1796             :     @Override
    1797             :     public long read() {
    1798          97 :       return actualTicker.read();
    1799             :     }
    1800             :   }
    1801             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750