All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gerrit.acceptance.AbstractDaemonTest Maven / Gradle / Ivy

There is a newer version: 3.11.0-rc4
Show newest version
// Copyright (C) 2013 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.gerrit.acceptance;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.OptionalSubject.optionals;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
import static com.google.gerrit.entities.Patch.COMMIT_MSG;
import static com.google.gerrit.entities.Patch.MERGE_LIST;
import static com.google.gerrit.extensions.api.changes.SubmittedTogetherOption.NON_VISIBLE_CHANGES;
import static com.google.gerrit.extensions.api.changes.SubmittedTogetherOption.TOPIC_CLOSURE;
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static com.google.gerrit.server.project.ProjectCache.illegalState;
import static com.google.gerrit.server.project.testing.TestLabels.label;
import static com.google.gerrit.server.project.testing.TestLabels.value;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static com.google.gerrit.testing.TestActionRefUpdateContext.testRefAction;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.toList;
import static org.eclipse.jgit.lib.Constants.HEAD;

import com.github.rholder.retry.BlockStrategy;
import com.google.common.base.Strings;
import com.google.common.base.Ticker;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.primitives.Chars;
import com.google.common.testing.FakeTicker;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.gerrit.acceptance.PushOneCommit.Result;
import com.google.gerrit.acceptance.testsuite.account.TestSshKeys;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.UsedAt;
import com.google.gerrit.entities.AccessSection;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.Address;
import com.google.gerrit.entities.BooleanProjectConfig;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.EmailHeader;
import com.google.gerrit.entities.EmailHeader.StringEmailHeader;
import com.google.gerrit.entities.GroupDescription;
import com.google.gerrit.entities.GroupReference;
import com.google.gerrit.entities.InternalGroup;
import com.google.gerrit.entities.LabelFunction;
import com.google.gerrit.entities.LabelType;
import com.google.gerrit.entities.LabelValue;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.Permission;
import com.google.gerrit.entities.PermissionRule;
import com.google.gerrit.entities.PermissionRule.Action;
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.entities.SubmitRequirement;
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.api.changes.ChangeApi;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.changes.RevisionApi;
import com.google.gerrit.extensions.api.changes.SubmittedTogetherInfo;
import com.google.gerrit.extensions.api.changes.SubmittedTogetherOption;
import com.google.gerrit.extensions.api.projects.BranchApi;
import com.google.gerrit.extensions.api.projects.BranchInfo;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.api.projects.ProjectInput;
import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.client.ProjectWatchInfo;
import com.google.gerrit.extensions.client.ReviewerState;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeType;
import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.common.DiffInfo;
import com.google.gerrit.extensions.common.EditInfo;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.json.OutputFormat;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.PluginUser;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.Accounts;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.change.BatchAbandon;
import com.google.gerrit.server.change.ChangeFinder;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.FileContentUtil;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.GerritInstanceId;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.PluginConfigFactory;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.index.account.AccountIndexer;
import com.google.gerrit.server.index.change.ChangeIndexer;
import com.google.gerrit.server.notedb.AbstractChangeNotes;
import com.google.gerrit.server.notedb.ChangeNoteUtil;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeNotesCommit;
import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk;
import com.google.gerrit.server.plugins.PluginContentScanner;
import com.google.gerrit.server.plugins.PluginGuiceEnvironment;
import com.google.gerrit.server.plugins.TestServerPlugin;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.server.restapi.change.Revisions;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.util.ManualRequestContext;
import com.google.gerrit.server.util.ThreadLocalRequestContext;
import com.google.gerrit.testing.ConfigSuite;
import com.google.gerrit.testing.FakeEmailSender;
import com.google.gerrit.testing.FakeEmailSender.Message;
import com.google.gson.Gson;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Provider;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevSort;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.Transport;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.rules.TemporaryFolder;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runner.RunWith;
import org.junit.runners.model.Statement;

@RunWith(ConfigSuite.class)
public abstract class AbstractDaemonTest {

  @ClassRule public static TemporaryFolder temporaryFolder = new TemporaryFolder();

  @ConfigSuite.Parameter public Config baseConfig;
  @ConfigSuite.Name public String configName;

  public TestRule testRunner =
      new TestRule() {
        @Override
        public Statement apply(Statement base, Description description) {
          return new Statement() {
            @Override
            public void evaluate() throws Throwable {
              beforeTest(description);
              try (ProjectResetter resetter =
                  server.createProjectResetter(
                      (allProjects, allUsers) -> resetProjects(allProjects, allUsers))) {
                base.evaluate();
              } finally {
                afterTest();
              }
            }
          };
        }
      };

  protected DaemonTestRule daemonTestRule;
  protected TestConfigRule configRule;
  protected ServerTestRule server;
  protected TimeSettingsTestRule timeSettingsRule;

  @Rule public TestRule topLevelTestRule = createTopLevelTestRule();

  /**
   * Creates test rules required for tests.
   *
   * 

Creating all rules in a single method gives more flexibility in the order of creation and * allows additional initialization steps if needed. */ protected TestRule createTopLevelTestRule() { daemonTestRule = createDaemonTestRule(); configRule = daemonTestRule.configRule(); timeSettingsRule = daemonTestRule.timeSettingsRule(); server = daemonTestRule.server(); return daemonTestRule; } @UsedAt(UsedAt.Project.GOOGLE) protected DaemonTestRule createDaemonTestRule() { return GerritServerDaemonTestRule.create(this); } @Inject @CanonicalWebUrl protected Provider canonicalWebUrl; @Inject @GerritPersonIdent protected Provider serverIdent; @Inject @GerritServerConfig protected Config cfg; @Inject @GerritInstanceId @Nullable protected String instanceId; @Inject protected AccountCache accountCache; @Inject protected AccountCreator accountCreator; @Inject protected Accounts accounts; @Inject protected AllProjectsName allProjects; @Inject protected AllUsersName allUsers; @Inject protected BatchUpdate.Factory batchUpdateFactory; @Inject protected ChangeData.Factory changeDataFactory; @Inject protected ChangeFinder changeFinder; @Inject protected ChangeIndexer indexer; @Inject protected ChangeNoteUtil changeNoteUtil; @Inject protected ChangeResource.Factory changeResourceFactory; @Inject protected FakeEmailSender sender; @Inject protected GerritApi gApi; @Inject protected GitRepositoryManager repoManager; @Inject protected GroupBackend groupBackend; @Inject protected GroupCache groupCache; @Inject protected IdentifiedUser.GenericFactory identifiedUserFactory; @Inject protected MetaDataUpdate.Server metaDataUpdateFactory; @Inject protected PatchSetUtil psUtil; @Inject protected ProjectCache projectCache; @Inject protected ProjectConfig.Factory projectConfigFactory; @Inject protected Provider queryProvider; @Inject protected PushOneCommit.Factory pushFactory; @Inject protected PluginConfigFactory pluginConfig; @Inject protected Revisions revisions; @Inject protected SystemGroupBackend systemGroupBackend; @Inject protected ChangeNotes.Factory notesFactory; @Inject protected BatchAbandon batchAbandon; @Inject protected TestSshKeys sshKeys; @Inject protected TestTicker testTicker; @Inject protected ThreadLocalRequestContext localCtx; @Nullable public SshSession adminSshSession; @Nullable public SshSession userSshSession; protected EventRecorder eventRecorder; protected Project.NameKey project; protected RestSession adminRestSession; protected RestSession userRestSession; protected RestSession anonymousRestSession; protected TestAccount admin; protected TestAccount user; protected TestRepository testRepo; protected String resourcePrefix; protected BlockStrategy noSleepBlockStrategy = t -> {}; // Don't sleep in tests. @Inject private AbstractChangeNotes.Args changeNotesArgs; @Inject private AccountIndexer accountIndexer; @Inject private EventRecorder.Factory eventRecorderFactory; @Inject private InProcessProtocol inProcessProtocol; @Inject private PluginGuiceEnvironment pluginGuiceEnvironment; @Inject private PluginUser.Factory pluginUserFactory; @Inject private RequestScopeOperations requestScopeOperations; @Inject private SitePaths sitePaths; @Inject private ProjectOperations projectOperations; private List toClose; public String getResourcePrefix() { return resourcePrefix; } public void setResourcePrefix(String resourcePrefix) { this.resourcePrefix = resourcePrefix; } public TestRepository getTestRepo() { return testRepo; } public void setTestRepo(TestRepository testRepo) { this.testRepo = testRepo; } public TestAccount getUser() { return user; } public void setUser(TestAccount user) { this.user = user; } public TestAccount getAdmin() { return admin; } public void setAdmin(TestAccount admin) { this.admin = admin; } public Project.NameKey getProject() { return project; } public void setProject(Project.NameKey project) { this.project = project; } @Before public void clearSender() { if (sender != null) { sender.clear(); } } @Before public void startEventRecorder() { if (eventRecorderFactory != null) { eventRecorder = eventRecorderFactory.create(admin); } } @After public void verifyNoPiiInChangeNotes() throws RestApiException, IOException { if (configRule.methodDescription().verifyNoPiiInChangeNotes()) { verifyNoAccountDetailsInChangeNotes(); } } @After public void closeEventRecorder() { if (eventRecorder != null) { eventRecorder.close(); } } @ConfigSuite.AfterConfig public static void stopCommonServer() throws Exception { GerritServerTestRule.afterConfigChanged(); } /** * Controls which project and branches should be reset in the commonServer after each test case. * *

Parameters allProjects and allUsers must refer to the commonServer names - if a test doesn't * use commonServer then names in the test can be different from names in commonServer. */ protected ProjectResetter.Config resetProjects( AllProjectsName allProjects, AllUsersName allUsers) { return new ProjectResetter.Config.Builder() // Don't reset all refs so that refs/sequences/changes is not touched and change IDs are // not reused. .reset(allProjects, RefNames.REFS_CONFIG) // Don't reset refs/sequences/accounts so that account IDs are not reused. .reset( allUsers, RefNames.REFS_CONFIG, RefNames.REFS_USERS + "*", RefNames.REFS_EXTERNAL_IDS, RefNames.REFS_GROUPNAMES, RefNames.REFS_GROUPS + "*", RefNames.REFS_STARRED_CHANGES + "*", RefNames.REFS_DRAFT_COMMENTS + "*") .build(); } protected void restartAsSlave() throws Exception { server.restartAsSlave(); server.getTestInjector().injectMembers(this); updateSshSessions(); } protected void restart() throws Exception { server.restart(); server.getTestInjector().injectMembers(this); updateSshSessions(); } public void reindexAccount(Account.Id accountId) { accountIndexer.index(accountId); } protected static Config submitWholeTopicEnabledConfig() { Config cfg = new Config(); cfg.setBoolean("change", null, "submitWholeTopic", true); return cfg; } protected boolean isSubmitWholeTopicEnabled() { return cfg.getBoolean("change", null, "submitWholeTopic", false); } protected boolean isContributorAgreementsEnabled() { return cfg.getBoolean("auth", null, "contributorAgreements", false); } protected void beforeTest(Description description) throws Exception { // SystemReader must be overridden before creating any repos, since they read the user/system // configs at initialization time, and are then stored in the RepositoryCache forever. if (enableExperimentsRejectImplicitMergesOnMerge()) { // When changes are merged/submitted - reject the operation if there is an implicit merge ( // even if rejectImplicitMerges is disabled in the project config). baseConfig.setStringList( "experiments", null, "enabled", ImmutableList.of( "GerritBackendFeature__check_implicit_merges_on_merge", "GerritBackendFeature__reject_implicit_merges_on_merge", "GerritBackendFeature__always_reject_implicit_merges_on_merge")); } server.initServer(); server.getTestInjector().injectMembers(this); Transport.register(inProcessProtocol); toClose = Collections.synchronizedList(new ArrayList<>()); setUpDatabase(); server.initSsh(); updateSshSessions(); } void updateSshSessions() throws Exception { userSshSession = null; adminSshSession = null; if (server.sshInitialized()) { try (ManualRequestContext ctx = requestScopeOperations.setNestedApiUser(user.id())) { userSshSession = server.getOrCreateSshSessionForContext(ctx); // The session doesn't store any reference to the context and it remains open after the ctx // is closed. userSshSession.open(); } try (ManualRequestContext ctx = requestScopeOperations.setNestedApiUser(admin.id())) { adminSshSession = server.getOrCreateSshSessionForContext(ctx); // The session doesn't store any reference to the context and it remains open after the ctx // is closed. adminSshSession.open(); } } } protected boolean enableExperimentsRejectImplicitMergesOnMerge() { // By default any attempt to make an explicit merge is rejected. This allows to check // that existing workflows continue to work even if gerrit rejects implicit merges on merge. return true; } protected void setUpDatabase() throws Exception { admin = accountCreator.admin(); user = accountCreator.user1(); // Evict and reindex accounts in case tests modify them. reindexAccount(admin.id()); reindexAccount(user.id()); adminRestSession = server.createRestSession(admin); userRestSession = server.createRestSession(user); anonymousRestSession = server.createRestSession(null); String testMethodName = configRule.description().getMethodName(); resourcePrefix = UNSAFE_PROJECT_NAME .matcher(configRule.description().getClassName() + "_" + testMethodName + "_") .replaceAll(""); requestScopeOperations.setApiUser(admin.id()); ProjectInput in = projectInput(configRule.description()); gApi.projects().create(in); project = Project.nameKey(in.name); if (!configRule.classDescription().skipProjectClone()) { testRepo = cloneProject(project, getCloneAsAccount(configRule.description())); } } /** Override to bind an additional Guice module */ public Module createModule() { return null; } /** Override to bind an alternative audit Guice module */ public Module createAuditModule() { return null; } /** Override to bind an additional Guice module for SSH injector */ public Module createSshModule() { return null; } protected TestAccount getCloneAsAccount(Description description) { TestProjectInput ann = description.getAnnotation(TestProjectInput.class); return accountCreator.get(ann != null ? ann.cloneAs() : "admin"); } /** Generate default project properties based on test description */ public ProjectInput projectInput(Description description) { ProjectInput in = new ProjectInput(); TestProjectInput ann = description.getAnnotation(TestProjectInput.class); in.name = name("project"); in.branches = ImmutableList.of(Constants.R_HEADS + Constants.MASTER); if (ann != null) { in.parent = Strings.emptyToNull(ann.parent()); in.description = Strings.emptyToNull(ann.description()); in.createEmptyCommit = ann.createEmptyCommit(); in.submitType = ann.submitType(); in.useContentMerge = ann.useContributorAgreements(); in.useSignedOffBy = ann.useSignedOffBy(); in.useContentMerge = ann.useContentMerge(); in.rejectEmptyCommit = ann.rejectEmptyCommit(); in.enableSignedPush = ann.enableSignedPush(); in.requireSignedPush = ann.requireSignedPush(); } else { // Defaults should match TestProjectConfig, omitting nullable values. in.createEmptyCommit = true; } updateProjectInput(in); return in; } /** * Modify a project input before creating the initial test project. * * @param in input; may be modified in place. */ protected void updateProjectInput(ProjectInput in) { // Default implementation does nothing. } public static final Pattern UNSAFE_PROJECT_NAME = Pattern.compile("[^a-zA-Z0-9._/-]+"); protected Git git() { return testRepo.git(); } protected InMemoryRepository repo() { return testRepo.getRepository(); } /** * Return a resource name scoped to this test method. * *

Test methods in a single class by default share a running server. For any resource name you * require to be unique to a test method, wrap it in a call to this method. * * @param name resource name (group, project, topic, etc.) * @return name prefixed by a string unique to this test method. */ protected String name(String name) { return daemonTestRule.name(name); } @CanIgnoreReturnValue protected Project.NameKey createProjectOverAPI( String nameSuffix, @Nullable Project.NameKey parent, boolean createEmptyCommit, @Nullable SubmitType submitType) throws RestApiException { ProjectInput in = new ProjectInput(); in.name = name(nameSuffix); in.parent = parent != null ? parent.get() : null; in.submitType = submitType; in.createEmptyCommit = createEmptyCommit; gApi.projects().create(in); return Project.nameKey(in.name); } protected TestRepository cloneProject(Project.NameKey p) throws Exception { return cloneProject(p, admin); } protected TestRepository cloneProject( Project.NameKey p, TestAccount testAccount) throws Exception { return daemonTestRule.cloneProject(p, testAccount); } /** * Register a repository connection over the test protocol. * * @return a URI string that can be used to connect to this repository for both fetch and push. */ String registerRepoConnection(Project.NameKey p, TestAccount testAccount) throws Exception { InProcessProtocol.Context ctx = new InProcessProtocol.Context(identifiedUserFactory, testAccount.id(), p); Repository repo = repoManager.openRepository(p); toClose.add(repo); return inProcessProtocol.register(ctx, repo).toString(); } protected void afterTest() throws Exception { Transport.unregister(inProcessProtocol); for (Repository repo : toClose) { repo.close(); } // Set useDefaultTicker in afterTest, so the next beforeTest will use the default ticker testTicker.useDefaultTicker(); } /** * Verify that NoteDB commits do not persist user-sensitive information, by running checks for all * commits in {@link RefNames#changeMetaRef} for all changes, created during the test. * *

These tests prevent regression, assuming appropriate test coverage for new features. The * verification is disabled by default and can be enabled using {@link VerifyNoPiiInChangeNotes} * annotation either on test class or method. */ protected void verifyNoAccountDetailsInChangeNotes() throws RestApiException, IOException { List allChanges = gApi.changes().query().get(); List allAccounts = accounts.all(); for (ChangeInfo change : allChanges) { try (Repository repo = repoManager.openRepository(Project.nameKey(change.project))) { String metaRefName = RefNames.changeMetaRef(Change.Id.tryParse(change._number.toString()).get()); ObjectId metaTip = repo.getRefDatabase().exactRef(metaRefName).getObjectId(); ChangeNotesRevWalk revWalk = ChangeNotesCommit.newRevWalk(repo); revWalk.reset(); revWalk.markStart(revWalk.parseCommit(metaTip)); ChangeNotesCommit commit; while ((commit = revWalk.next()) != null) { String fullMessage = commit.getFullMessage(); for (AccountState accountState : allAccounts) { Account account = accountState.account(); assertThat(fullMessage).doesNotContain(account.getName()); if (account.fullName() != null) { assertThat(fullMessage).doesNotContain(account.fullName()); } if (account.displayName() != null) { assertThat(fullMessage).doesNotContain(account.displayName()); } if (account.preferredEmail() != null) { assertThat(fullMessage).doesNotContain(account.preferredEmail()); } if (accountState.userName().isPresent()) { assertThat(fullMessage).doesNotContain(accountState.userName().get()); } ImmutableList allEmails = accountState.externalIds().stream() .map(ExternalId::email) .filter(Objects::nonNull) .collect(toImmutableList()); for (String email : allEmails) { assertThat(fullMessage).doesNotContain(email); } } } } } } protected TestRepository.CommitBuilder commitBuilder() throws Exception { return testRepo.branch("HEAD").commit().insertChangeId(); } protected TestRepository.CommitBuilder amendBuilder() throws Exception { ObjectId head = repo().exactRef("HEAD").getObjectId(); TestRepository.CommitBuilder b = testRepo.amendRef("HEAD"); Optional id = GitUtil.getChangeId(testRepo, head); // TestRepository behaves like "git commit --amend -m foo", which does not // preserve an existing Change-Id. Tests probably want this. if (id.isPresent()) { b.insertChangeId(id.get().substring(1)); } else { b.insertChangeId(); } return b; } @CanIgnoreReturnValue protected PushOneCommit.Result createChange() throws Exception { return createChange("refs/for/master"); } @CanIgnoreReturnValue protected PushOneCommit.Result createChange(String ref) throws Exception { PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo); PushOneCommit.Result result = push.to(ref); result.assertOkStatus(); return result; } @CanIgnoreReturnValue protected PushOneCommit.Result createChange(TestRepository repo) throws Exception { PushOneCommit push = pushFactory.create(admin.newIdent(), repo); PushOneCommit.Result result = push.to("refs/for/master"); result.assertOkStatus(); return result; } @CanIgnoreReturnValue protected PushOneCommit.Result createMergeCommitChange(String ref) throws Exception { return createMergeCommitChange(ref, "foo"); } @CanIgnoreReturnValue protected PushOneCommit.Result createMergeCommitChange(String ref, String file) throws Exception { ObjectId initial = repo().exactRef(HEAD).getLeaf().getObjectId(); PushOneCommit.Result p1 = pushFactory .create( admin.newIdent(), testRepo, "parent 1", ImmutableMap.of(file, "foo-1", "bar", "bar-1")) .to(ref); // reset HEAD in order to create a sibling of the first change testRepo.reset(initial); PushOneCommit.Result p2 = pushFactory .create( admin.newIdent(), testRepo, "parent 2", ImmutableMap.of(file, "foo-2", "bar", "bar-2")) .to(ref); PushOneCommit m = pushFactory.create( admin.newIdent(), testRepo, "merge", ImmutableMap.of(file, "foo-1", "bar", "bar-2")); m.setParents(ImmutableList.of(p1.getCommit(), p2.getCommit())); PushOneCommit.Result result = m.to(ref); result.assertOkStatus(); return result; } @CanIgnoreReturnValue protected PushOneCommit.Result createNParentsMergeCommitChange(String ref, List fileNames) throws Exception { // This method creates n different commits and creates a merge commit pointing to all n parents. // Each commit will contain all the fileNames. Commit i will have the following file names and // their contents: // {$file_1_name, ${file_1_name}-1} // {$file_2_name, ${file_2_name}-1}, etc... // The merge commit will have: // {$file_1_name, ${file_1_name}-1} // {$file_2_name, ${file_2_name}-2}, // {$file_3_name, ${file_3_name}-3}, etc... // i.e. taking the ith file from the ith commit. int n = fileNames.size(); ObjectId initial = repo().exactRef(HEAD).getLeaf().getObjectId(); List pushResults = new ArrayList<>(); for (int i = 1; i <= n; i++) { int finalI = i; pushResults.add( pushFactory .create( admin.newIdent(), testRepo, "parent " + i, fileNames.stream().collect(Collectors.toMap(f -> f, f -> f + "-" + finalI))) .to(ref)); // reset HEAD in order to create a sibling of the first change if (i < n) { testRepo.reset(initial); } } PushOneCommit m = pushFactory.create( admin.newIdent(), testRepo, "merge", IntStream.range(1, n + 1) .boxed() .collect( Collectors.toMap( i -> fileNames.get(i - 1), i -> fileNames.get(i - 1) + "-" + i))); m.setParents(pushResults.stream().map(PushOneCommit.Result::getCommit).collect(toList())); PushOneCommit.Result result = m.to(ref); result.assertOkStatus(); return result; } @CanIgnoreReturnValue protected PushOneCommit.Result createCommitAndPush( TestRepository repo, String ref, String commitMsg, String fileName, String content) throws Exception { PushOneCommit.Result result = pushFactory.create(admin.newIdent(), repo, commitMsg, fileName, content).to(ref); result.assertOkStatus(); return result; } @CanIgnoreReturnValue protected PushOneCommit.Result createChangeWithTopic( TestRepository repo, String topic, String commitMsg, String fileName, String content) throws Exception { assertThat(topic).isNotEmpty(); return createCommitAndPush( repo, "refs/for/master%topic=" + name(topic), commitMsg, fileName, content); } @CanIgnoreReturnValue protected PushOneCommit.Result createChange(String subject, String fileName, String content) throws Exception { PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo, subject, fileName, content); return push.to("refs/for/master"); } @CanIgnoreReturnValue protected PushOneCommit.Result createChange( TestRepository repo, String branch, String subject, String fileName, String content, @Nullable String topic) throws Exception { PushOneCommit push = pushFactory.create(admin.newIdent(), repo, subject, fileName, content); return push.to( "refs/for/" + branch + (Strings.isNullOrEmpty(topic) ? "" : "%topic=" + name(topic))); } @CanIgnoreReturnValue protected BranchApi createBranch(BranchNameKey branch) throws Exception { return gApi.projects() .name(branch.project().get()) .branch(branch.branch()) .create(new BranchInput()); } @CanIgnoreReturnValue protected BranchApi createBranchWithRevision(BranchNameKey branch, String revision) throws Exception { BranchInput in = new BranchInput(); in.revision = revision; return gApi.projects().name(branch.project().get()).branch(branch.branch()).create(in); } private static final List RANDOM = Chars.asList('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'); @CanIgnoreReturnValue protected PushOneCommit.Result amendChangeWithUploader( PushOneCommit.Result change, Project.NameKey projectName, TestAccount account) throws Exception { TestRepository repo = cloneProject(projectName, account); GitUtil.fetch(repo, "refs/*:refs/*"); repo.reset(change.getCommit()); PushOneCommit.Result result = amendChange( change.getChangeId(), "refs/for/master", account, repo, "new subject", "new file", "new content " + UUID.randomUUID()); return result; } @CanIgnoreReturnValue protected PushOneCommit.Result amendChange(String changeId) throws Exception { return amendChange(changeId, "refs/for/master", admin, testRepo); } @CanIgnoreReturnValue protected PushOneCommit.Result amendChange( String changeId, String ref, TestAccount testAccount, TestRepository repo) throws Exception { Collections.shuffle(RANDOM); return amendChange( changeId, ref, testAccount, repo, PushOneCommit.SUBJECT, PushOneCommit.FILE_NAME, new String(Chars.toArray(RANDOM))); } @CanIgnoreReturnValue protected PushOneCommit.Result amendChange( String changeId, String subject, String fileName, String content) throws Exception { return amendChange(changeId, "refs/for/master", admin, testRepo, subject, fileName, content); } @CanIgnoreReturnValue protected PushOneCommit.Result amendChange( String changeId, String ref, TestAccount testAccount, TestRepository repo, String subject, String fileName, String content) throws Exception { PushOneCommit push = pushFactory.create(testAccount.newIdent(), repo, subject, fileName, content, changeId); return push.to(ref); } protected void merge(PushOneCommit.Result r) throws Exception { revision(r).review(ReviewInput.approve()); revision(r).submit(); } protected ChangeInfo info(String id) throws RestApiException { return gApi.changes().id(id).info(); } protected ChangeApi change(Result r) throws RestApiException { return gApi.changes().id(r.getChange().getId().get()); } protected Optional getEdit(String id) throws RestApiException { return gApi.changes().id(id).edit().get(); } protected ChangeInfo get(String id, ListChangesOption... options) throws RestApiException { return gApi.changes().id(id).get(options); } protected AccountInfo getAccountInfo(Account.Id accountId) throws RestApiException { return gApi.accounts().id(accountId.get()).get(); } protected List query(String q) throws RestApiException { return gApi.changes().query(q).get(); } protected Account getAccount(Account.Id accountId) { return getAccountState(accountId).account(); } protected AccountState getAccountState(Account.Id accountId) { Optional accountState = accountCache.get(accountId); assertWithMessage("account %s", accountId.get()) .about(optionals()) .that(accountState) .isPresent(); return accountState.get(); } protected AutoCloseable disableNoteDb() { changeNotesArgs.failOnLoadForTest.set(true); return () -> { changeNotesArgs.failOnLoadForTest.set(false); }; } protected static Gson newGson() { return OutputFormat.JSON_COMPACT.newGson(); } protected RevisionApi revision(PushOneCommit.Result r) throws Exception { return gApi.changes().id(r.getChangeId()).current(); } protected void setUseSignedOffBy(InheritableBoolean value) throws Exception { try (MetaDataUpdate md = metaDataUpdateFactory.create(project)) { ProjectConfig config = projectConfigFactory.read(md); config.updateProject(p -> p.setBooleanConfig(BooleanProjectConfig.USE_SIGNED_OFF_BY, value)); config.commit(md); projectCache.evictAndReindex(config.getProject()); } } protected void setRequireChangeId(InheritableBoolean value) throws Exception { try (MetaDataUpdate md = metaDataUpdateFactory.create(project)) { ProjectConfig config = projectConfigFactory.read(md); config.updateProject(p -> p.setBooleanConfig(BooleanProjectConfig.REQUIRE_CHANGE_ID, value)); config.commit(md); projectCache.evictAndReindex(config.getProject()); } } protected void setSkipAddingAuthorAndCommitterAsReviewers(InheritableBoolean value) throws Exception { try (MetaDataUpdate md = metaDataUpdateFactory.create(project)) { ProjectConfig config = projectConfigFactory.read(md); config.updateProject( p -> p.setBooleanConfig( BooleanProjectConfig.SKIP_ADDING_AUTHOR_AND_COMMITTER_AS_REVIEWERS, value)); config.commit(md); projectCache.evictAndReindex(config.getProject()); } } protected void blockAnonymousRead() throws Exception { String allRefs = RefNames.REFS + "*"; projectOperations .project(project) .forUpdate() .add(block(Permission.READ).ref(allRefs).group(ANONYMOUS_USERS)) .add(allow(Permission.READ).ref(allRefs).group(REGISTERED_USERS)) .update(); } @CanIgnoreReturnValue protected PushOneCommit.Result pushTo(String ref) throws Exception { PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo); return push.to(ref); } protected void approve(String id) throws Exception { gApi.changes().id(id).current().review(ReviewInput.approve()); } protected void recommend(String id) throws Exception { gApi.changes().id(id).current().review(ReviewInput.recommend()); } protected void assertThatAccountIsNotVisible(TestAccount... testAccounts) { for (TestAccount testAccount : testAccounts) { assertThrows( ResourceNotFoundException.class, () -> gApi.accounts().id(testAccount.id().get()).get()); } } protected void assertReviewers(String changeId, TestAccount... expectedReviewers) throws RestApiException { Map> reviewerMap = gApi.changes().id(changeId).get().reviewers; assertThat(reviewerMap).containsKey(ReviewerState.REVIEWER); List reviewers = reviewerMap.get(ReviewerState.REVIEWER).stream().map(a -> a._accountId).collect(toList()); assertThat(reviewers) .containsExactlyElementsIn( Arrays.stream(expectedReviewers).map(a -> a.id().get()).collect(toList())); } protected void assertCcs(String changeId, TestAccount... expectedCcs) throws RestApiException { Map> reviewerMap = gApi.changes().id(changeId).get().reviewers; assertThat(reviewerMap).containsKey(ReviewerState.CC); List ccs = reviewerMap.get(ReviewerState.CC).stream().map(a -> a._accountId).collect(toList()); assertThat(ccs) .containsExactlyElementsIn( Arrays.stream(expectedCcs).map(a -> a.id().get()).collect(toList())); } protected void assertNoCcs(String changeId) throws RestApiException { Map> reviewerMap = gApi.changes().id(changeId).get().reviewers; assertThat(reviewerMap).doesNotContainKey(ReviewerState.CC); } protected void assertSubmittedTogether(String chId, String... expected) throws Exception { assertSubmittedTogether(chId, ImmutableSet.of(), expected); } protected void assertSubmittedTogetherWithTopicClosure(String chId, String... expected) throws Exception { assertSubmittedTogether(chId, ImmutableSet.of(TOPIC_CLOSURE), expected); } protected void assertSubmittedTogether( String chId, ImmutableSet submittedTogetherOptions, String... expected) throws Exception { // This does not include NON_VISIBILE_CHANGES List actual = submittedTogetherOptions.isEmpty() ? gApi.changes().id(chId).submittedTogether() : gApi.changes() .id(chId) .submittedTogether(EnumSet.copyOf(submittedTogetherOptions)) .changes; EnumSet enumSetIncludingNonVisibleChanges = submittedTogetherOptions.isEmpty() ? EnumSet.of(NON_VISIBLE_CHANGES) : EnumSet.copyOf(submittedTogetherOptions); enumSetIncludingNonVisibleChanges.add(NON_VISIBLE_CHANGES); // This includes NON_VISIBLE_CHANGES for comparison. SubmittedTogetherInfo info = gApi.changes().id(chId).submittedTogether(enumSetIncludingNonVisibleChanges); assertThat(info.nonVisibleChanges).isEqualTo(0); assertThat(Iterables.transform(actual, i1 -> i1.changeId)) .containsExactly((Object[]) expected) .inOrder(); assertThat(Iterables.transform(info.changes, i -> i.changeId)) .containsExactly((Object[]) expected) .inOrder(); } protected PatchSet getPatchSet(PatchSet.Id psId) { return changeDataFactory.create(project, psId.changeId()).patchSet(psId); } protected IdentifiedUser user(TestAccount testAccount) { return identifiedUserFactory.create(testAccount.id()); } protected RevisionResource parseCurrentRevisionResource(String changeId) throws Exception { ChangeResource cr = parseChangeResource(changeId); int psId = cr.getChange().currentPatchSetId().get(); return revisions.parse(cr, IdString.fromDecoded(Integer.toString(psId))); } protected RevisionResource parseRevisionResource(String changeId, int n) throws Exception { return revisions.parse( parseChangeResource(changeId), IdString.fromDecoded(Integer.toString(n))); } protected RevisionResource parseRevisionResource(PushOneCommit.Result r) throws Exception { PatchSet.Id psId = r.getPatchSetId(); return parseRevisionResource(psId.changeId().toString(), psId.get()); } protected ChangeResource parseChangeResource(String changeId) throws Exception { List notes = changeFinder.find(changeId); assertThat(notes).hasSize(1); return changeResourceFactory.create(notes.get(0), localCtx.getContext().getUser()); } protected RevCommit getHead(Repository repo, String name) throws Exception { try (RevWalk rw = new RevWalk(repo)) { Ref r = repo.exactRef(name); return rw.parseCommit(r.getObjectId()); } } protected void assertMailReplyTo(Message message, String email) throws Exception { assertThat(message.headers()).containsKey("Reply-To"); StringEmailHeader replyTo = (StringEmailHeader) message.headers().get("Reply-To"); assertThat(replyTo.getString()).contains(email); } protected void assertMailNotReplyTo(Message message, String email) throws Exception { assertThat(message.headers()).containsKey("Reply-To"); StringEmailHeader replyTo = (StringEmailHeader) message.headers().get("Reply-To"); assertThat(replyTo.getString()).doesNotContain(email); } /** Assert that the given branches have the given tree ids. */ protected void assertTrees(Project.NameKey proj, Map trees) throws Exception { TestRepository localRepo = cloneProject(proj); GitUtil.fetch(localRepo, "refs/*:refs/*"); Map refValues = new HashMap<>(); for (BranchNameKey b : trees.keySet()) { if (!b.project().equals(proj)) { continue; } Ref r = localRepo.getRepository().exactRef(b.branch()); assertThat(r).isNotNull(); RevWalk rw = localRepo.getRevWalk(); RevCommit c = rw.parseCommit(r.getObjectId()); refValues.put(b, c.getTree()); assertThat(trees.get(b)).isEqualTo(refValues.get(b)); } assertThat(refValues.keySet()).containsAnyIn(trees.keySet()); } protected void assertDiffForFullyModifiedFile( DiffInfo diff, String commitName, String path, String expectedContentSideA, String expectedContentSideB) throws Exception { assertDiffForFile(diff, commitName, path); ImmutableList expectedOldLines = ImmutableList.copyOf(expectedContentSideA.split("\n", -1)); ImmutableList expectedNewLines = ImmutableList.copyOf(expectedContentSideB.split("\n", -1)); assertThat(diff.changeType).isEqualTo(ChangeType.MODIFIED); assertThat(diff.metaA).isNotNull(); assertThat(diff.metaB).isNotNull(); assertThat(diff.metaA.name).isEqualTo(path); assertThat(diff.metaA.lines).isEqualTo(expectedOldLines.size()); assertThat(diff.metaB.name).isEqualTo(path); assertThat(diff.metaB.lines).isEqualTo(expectedNewLines.size()); DiffInfo.ContentEntry contentEntry = diff.content.get(0); assertThat(contentEntry.a).containsExactlyElementsIn(expectedOldLines).inOrder(); assertThat(contentEntry.b).containsExactlyElementsIn(expectedNewLines).inOrder(); assertThat(contentEntry.ab).isNull(); assertThat(contentEntry.common).isNull(); assertThat(contentEntry.editA).isNull(); assertThat(contentEntry.editB).isNull(); assertThat(contentEntry.skip).isNull(); } protected void assertDiffForNewFile( DiffInfo diff, @Nullable RevCommit commit, String path, String expectedContentSideB) throws Exception { assertDiffForNewFile(diff, commit.name(), path, expectedContentSideB); } protected void assertDiffForNewFile( DiffInfo diff, String commitName, String path, String expectedContentSideB) throws Exception { assertDiffForFile(diff, commitName, path); ImmutableList expectedLines = ImmutableList.copyOf(expectedContentSideB.split("\n", -1)); assertThat(diff.changeType).isEqualTo(ChangeType.ADDED); assertThat(diff.metaA).isNull(); assertThat(diff.metaB).isNotNull(); assertThat(diff.metaB.name).isEqualTo(path); assertThat(diff.metaB.lines).isEqualTo(expectedLines.size()); DiffInfo.ContentEntry contentEntry = diff.content.get(0); assertThat(contentEntry.b).containsExactlyElementsIn(expectedLines).inOrder(); assertThat(contentEntry.a).isNull(); assertThat(contentEntry.ab).isNull(); assertThat(contentEntry.common).isNull(); assertThat(contentEntry.editA).isNull(); assertThat(contentEntry.editB).isNull(); assertThat(contentEntry.skip).isNull(); } protected void assertDiffForDeletedFile(DiffInfo diff, String path, String expectedContentSideA) throws Exception { assertDiffHeaders(diff); ImmutableList expectedOriginalLines = ImmutableList.copyOf(expectedContentSideA.split("\n", -1)); assertThat(diff.changeType).isEqualTo(ChangeType.DELETED); assertThat(diff.metaA).isNotNull(); assertThat(diff.metaB).isNull(); assertThat(diff.metaA.name).isEqualTo(path); assertThat(diff.metaA.lines).isEqualTo(expectedOriginalLines.size()); DiffInfo.ContentEntry contentEntry = diff.content.get(0); assertThat(contentEntry.a).containsExactlyElementsIn(expectedOriginalLines).inOrder(); assertThat(contentEntry.b).isNull(); assertThat(contentEntry.ab).isNull(); assertThat(contentEntry.common).isNull(); assertThat(contentEntry.editA).isNull(); assertThat(contentEntry.editB).isNull(); assertThat(contentEntry.skip).isNull(); } private void assertDiffForFile(DiffInfo diff, String commitName, String path) throws Exception { assertDiffHeaders(diff); assertThat(diff.metaB.commitId).isEqualTo(commitName); String expectedContentType = "text/plain"; if (COMMIT_MSG.equals(path)) { expectedContentType = FileContentUtil.TEXT_X_GERRIT_COMMIT_MESSAGE; } else if (MERGE_LIST.equals(path)) { expectedContentType = FileContentUtil.TEXT_X_GERRIT_MERGE_LIST; } assertThat(diff.metaB.contentType).isEqualTo(expectedContentType); assertThat(diff.metaB.name).isEqualTo(path); assertThat(diff.metaB.webLinks).isNull(); } private void assertDiffHeaders(DiffInfo diff) throws Exception { assertThat(diff.binary).isNull(); assertThat(diff.diffHeader).isNotNull(); assertThat(diff.intralineStatus).isNull(); assertThat(diff.webLinks).isNull(); assertThat(diff.editWebLinks).isNull(); } protected void assertPermitted(ChangeInfo info, String label, Integer... expected) { assertThat(info.permittedLabels).isNotNull(); Collection strs = info.permittedLabels.get(label); if (expected.length == 0) { assertThat(strs).isNull(); } else { assertThat(strs.stream().map(s -> Integer.valueOf(s.trim())).collect(toList())) .containsExactlyElementsIn(Arrays.asList(expected)); } } protected void assertOnlyRemovableLabel( ChangeInfo info, String labelId, String labelValue, TestAccount reviewer) { assertThat(info.removableLabels).hasSize(1); assertThat(info.removableLabels).containsKey(labelId); assertThat(info.removableLabels.get(labelId)).hasSize(1); assertThat(info.removableLabels.get(labelId)).containsKey(labelValue); assertThat(info.removableLabels.get(labelId).get(labelValue)).hasSize(1); assertThat(info.removableLabels.get(labelId).get(labelValue).get(0).email) .isEqualTo(reviewer.email()); } protected void assertPermissions( Project.NameKey project, GroupReference groupReference, String ref, boolean exclusive, String... permissionNames) { Optional accessSection = projectCache .get(project) .orElseThrow(illegalState(project)) .getConfig() .getAccessSection(ref); assertThat(accessSection).isPresent(); for (String permissionName : permissionNames) { Permission permission = accessSection.get().getPermission(permissionName); assertPermission(permission, permissionName, exclusive, null); assertPermissionRule( permission.getRule(groupReference), groupReference, Action.ALLOW, false, 0, 0); } } protected void assertPermission( Permission permission, String expectedName, boolean expectedExclusive, @Nullable String expectedLabelName) { assertThat(permission).isNotNull(); assertThat(permission.getName()).isEqualTo(expectedName); assertThat(permission.getExclusiveGroup()).isEqualTo(expectedExclusive); assertThat(permission.getLabel()).isEqualTo(expectedLabelName); } protected void assertPermissionRule( PermissionRule rule, GroupReference expectedGroupReference, Action expectedAction, boolean expectedForce, int expectedMin, int expectedMax) { assertThat(rule.getGroup()).isEqualTo(expectedGroupReference); assertThat(rule.getAction()).isEqualTo(expectedAction); assertThat(rule.getForce()).isEqualTo(expectedForce); assertThat(rule.getMin()).isEqualTo(expectedMin); assertThat(rule.getMax()).isEqualTo(expectedMax); } protected void assertHead(String projectName, String expectedRef) throws Exception { // Assert gerrit's project head points to the correct branch assertThat(getProjectBranches(projectName).get(Constants.HEAD).revision) .isEqualTo(RefNames.shortName(expectedRef)); // Assert git head points to the correct branch try (Repository repo = repoManager.openRepository(Project.nameKey(projectName))) { assertThat(repo.exactRef(Constants.HEAD).getTarget().getName()).isEqualTo(expectedRef); } } protected InternalGroup group(AccountGroup.UUID groupUuid) { InternalGroup group = groupCache.get(groupUuid).orElse(null); assertWithMessage(groupUuid.get()).that(group).isNotNull(); return group; } protected GroupReference groupRef(AccountGroup.UUID groupUuid) { GroupDescription.Basic groupDescription = groupBackend.get(groupUuid); return GroupReference.create(groupDescription.getGroupUUID(), groupDescription.getName()); } protected InternalGroup group(String groupName) { InternalGroup group = groupCache.get(AccountGroup.nameKey(groupName)).orElse(null); assertWithMessage(groupName).that(group).isNotNull(); return group; } protected GroupReference groupRef(String groupName) { InternalGroup group = groupCache.get(AccountGroup.nameKey(groupName)).orElse(null); assertThat(group).isNotNull(); return GroupReference.create(group.getGroupUUID(), group.getName()); } protected AccountGroup.UUID groupUuid(String groupName) { return group(groupName).getGroupUUID(); } protected InternalGroup adminGroup() { return group("Administrators"); } protected GroupReference adminGroupRef() { return groupRef("Administrators"); } protected AccountGroup.UUID adminGroupUuid() { return groupUuid("Administrators"); } protected void assertGroupDoesNotExist(String groupName) { InternalGroup group = groupCache.get(AccountGroup.nameKey(groupName)).orElse(null); assertWithMessage(groupName).that(group).isNull(); } protected void assertNotifyTo(TestAccount expected) { assertNotifyTo(expected.email(), expected.fullName()); } protected void assertNotifyTo(String expectedEmail, String expectedFullname) { Address expectedAddress = Address.create(expectedFullname, expectedEmail); assertThat(sender.getMessages()).hasSize(1); Message m = sender.getMessages().get(0); assertThat(m.rcpt()).containsExactly(expectedAddress); assertThat(((EmailHeader.AddressList) m.headers().get("To")).getAddressList()) .containsExactly(expectedAddress); assertThat(m.headers().get("Cc").isEmpty()).isTrue(); } protected void assertNotifyCc(TestAccount expected) { assertNotifyCc(expected.getNameEmail()); } protected void assertNotifyCc(String expectedEmail, String expectedFullname) { Address expectedAddress = Address.create(expectedFullname, expectedEmail); assertNotifyCc(expectedAddress); } protected void assertNotifyCc(Address expectedAddress) { assertThat(sender.getMessages()).hasSize(1); Message m = sender.getMessages().get(0); assertThat(m.rcpt()).containsExactly(expectedAddress); assertThat(m.headers().get("To").isEmpty()).isTrue(); assertThat(((EmailHeader.AddressList) m.headers().get("Cc")).getAddressList()) .containsExactly(expectedAddress); } protected void assertNotifyBcc(TestAccount expected) { assertThat(sender.getMessages()).hasSize(1); Message m = sender.getMessages().get(0); assertThat(m.rcpt()).containsExactly(expected.getNameEmail()); assertThat(m.headers().get("To").isEmpty()).isTrue(); assertThat(m.headers().get("Cc").isEmpty()).isTrue(); } protected void assertNotifyBcc(String expectedEmail, String expectedFullName) { assertThat(sender.getMessages()).hasSize(1); Message m = sender.getMessages().get(0); assertThat(m.rcpt()).containsExactly(Address.create(expectedFullName, expectedEmail)); assertThat(m.headers().get("To").isEmpty()).isTrue(); assertThat(m.headers().get("Cc").isEmpty()).isTrue(); } protected interface ProjectWatchInfoConfiguration { void configure(ProjectWatchInfo pwi); } protected void watch(String project, ProjectWatchInfoConfiguration config) throws RestApiException { ProjectWatchInfo pwi = new ProjectWatchInfo(); pwi.project = project; config.configure(pwi); gApi.accounts().self().setWatchedProjects(ImmutableList.of(pwi)); } protected void watch(PushOneCommit.Result r, ProjectWatchInfoConfiguration config) throws RestApiException { watch(r.getChange().project().get(), config); } protected void watch(String project, String filter) throws RestApiException { watch( project, pwi -> { pwi.filter = filter; pwi.notifyAbandonedChanges = true; pwi.notifyNewChanges = true; pwi.notifyNewPatchSets = true; pwi.notifyAllComments = true; }); } protected void watch(String project) throws RestApiException { watch(project, (String) null); } protected void assertContent(PushOneCommit.Result pushResult, String path, String expectedContent) throws Exception { BinaryResult bin = gApi.changes() .id(pushResult.getChangeId()) .revision(pushResult.getCommit().name()) .file(path) .content(); ByteArrayOutputStream os = new ByteArrayOutputStream(); bin.writeTo(os); String res = new String(os.toByteArray(), UTF_8); assertThat(res).isEqualTo(expectedContent); } @CanIgnoreReturnValue protected RevCommit createNewCommitWithoutChangeId(String branch, String file, String content) throws Exception { try (Repository repo = repoManager.openRepository(project); RevWalk walk = new RevWalk(repo)) { Ref ref = repo.exactRef(branch); RevCommit tip = null; if (ref != null) { tip = walk.parseCommit(ref.getObjectId()); } TestRepository testSrcRepo = new TestRepository<>(repo); TestRepository.BranchBuilder builder = testSrcRepo.branch(branch); RevCommit revCommit = tip == null ? builder.commit().message("commit 1").add(file, content).create() : builder.commit().parent(tip).message("commit 1").add(file, content).create(); assertThat(GitUtil.getChangeId(testSrcRepo, revCommit)).isEmpty(); return revCommit; } } protected RevCommit parseCurrentRevision(RevWalk rw, String changeId) throws Exception { return rw.parseCommit( ObjectId.fromString(get(changeId, ListChangesOption.CURRENT_REVISION).currentRevision)); } protected void configSubmitRequirement( Project.NameKey project, SubmitRequirement submitRequirement) throws Exception { try (ProjectConfigUpdate u = updateProject(project)) { u.getConfig().upsertSubmitRequirement(submitRequirement); u.save(); } } protected void clearSubmitRequirements(Project.NameKey project) throws Exception { try (ProjectConfigUpdate u = updateProject(project)) { u.getConfig().clearSubmitRequirements(); u.save(); } } protected void configLabel(String label, LabelFunction func) throws Exception { configLabel(label, func, ImmutableList.of()); } protected void configLabel(String label, LabelFunction func, List refPatterns) throws Exception { configLabel( project, label, func, refPatterns, value(1, "Passes"), value(0, "No score"), value(-1, "Failed")); } protected void configLabel( Project.NameKey project, String label, LabelFunction func, LabelValue... value) throws Exception { configLabel(project, label, func, ImmutableList.of(), value); } @SuppressWarnings("deprecation") private void configLabel( Project.NameKey project, String label, LabelFunction func, List refPatterns, LabelValue... value) throws Exception { try (ProjectConfigUpdate u = updateProject(project)) { LabelType.Builder labelType = label(label, value).toBuilder(); labelType.setFunction(func); labelType.setRefPatterns(ImmutableList.copyOf(refPatterns)); u.getConfig().upsertLabelType(labelType.build()); u.save(); } } protected void enableCreateNewChangeForAllNotInTarget() throws Exception { try (ProjectConfigUpdate u = updateProject(project)) { u.getConfig() .updateProject( p -> p.setBooleanConfig( BooleanProjectConfig.CREATE_NEW_CHANGE_FOR_ALL_NOT_IN_TARGET, InheritableBoolean.TRUE)); u.save(); } } protected ProjectConfigUpdate updateProject(Project.NameKey projectName) throws Exception { return new ProjectConfigUpdate(projectName); } public class ProjectConfigUpdate implements AutoCloseable { private final ProjectConfig projectConfig; private MetaDataUpdate metaDataUpdate; private ProjectConfigUpdate(Project.NameKey projectName) throws Exception { metaDataUpdate = metaDataUpdateFactory.create(projectName); projectConfig = projectConfigFactory.read(metaDataUpdate); } public ProjectConfig getConfig() { return projectConfig; } public void save() throws Exception { testRefAction( () -> { metaDataUpdate.setAuthor(identifiedUserFactory.create(admin.id())); projectConfig.commit(metaDataUpdate); metaDataUpdate.close(); metaDataUpdate = null; projectCache.evictAndReindex(projectConfig.getProject()); }); } @Override public void close() { if (metaDataUpdate != null) { metaDataUpdate.close(); } } } protected List getChangeMetaCommitsInReverseOrder(Change.Id changeId) throws IOException { try (Repository repo = repoManager.openRepository(project); RevWalk revWalk = new RevWalk(repo)) { revWalk.sort(RevSort.TOPO); revWalk.sort(RevSort.REVERSE); Ref metaRef = repo.exactRef(RefNames.changeMetaRef(changeId)); revWalk.markStart(revWalk.parseCommit(metaRef.getObjectId())); return Lists.newArrayList(revWalk); } } protected List getChangeSortedComments(int changeNum) throws Exception { List comments = new ArrayList<>(); Map> commentsMap = gApi.changes().id(changeNum).commentsRequest().get(); for (Map.Entry> e : commentsMap.entrySet()) { for (CommentInfo c : e.getValue()) { c.path = e.getKey(); // Set the comment's path field. comments.add(c); } } comments.sort(Comparator.comparing(c -> c.id)); return comments; } protected ImmutableMap getProjectBranches(String projectName) throws RestApiException { return gApi.projects().name(projectName).branches().get().stream() .collect(ImmutableMap.toImmutableMap(branch -> branch.ref, branch -> branch)); } protected void createRefLogFileIfMissing(Repository repo, String ref) throws IOException { File log = new File(repo.getDirectory(), "logs/" + ref); if (!log.exists()) { log.getParentFile().mkdirs(); assertThat(log.createNewFile()).isTrue(); } } protected AutoCloseable installPlugin(String pluginName, Class sysModuleClass) throws Exception { return installPlugin(pluginName, sysModuleClass, null, null); } protected AutoCloseable installPlugin( String pluginName, @Nullable Class sysModuleClass, @Nullable Class httpModuleClass, @Nullable Class sshModuleClass) throws Exception { return installPlugin(pluginName, sysModuleClass, httpModuleClass, sshModuleClass, null); } protected AutoCloseable installPlugin( String pluginName, @Nullable Class sysModuleClass, @Nullable Class httpModuleClass, @Nullable Class sshModuleClass, PluginContentScanner scanner) throws Exception { checkStatic(sysModuleClass); checkStatic(httpModuleClass); checkStatic(sshModuleClass); TestServerPlugin plugin = new TestServerPlugin( pluginName, "http://example.com/" + pluginName, pluginUserFactory.create(pluginName), scanner, getClass().getClassLoader(), sysModuleClass != null ? sysModuleClass.getName() : null, httpModuleClass != null ? httpModuleClass.getName() : null, sshModuleClass != null ? sshModuleClass.getName() : null, sitePaths.data_dir.resolve(pluginName)); plugin.start(pluginGuiceEnvironment); pluginGuiceEnvironment.onStartPlugin(plugin); return () -> { plugin.stop(pluginGuiceEnvironment); pluginGuiceEnvironment.onStopPlugin(plugin); }; } private static void checkStatic(@Nullable Class moduleClass) { if (moduleClass != null) { checkArgument( (moduleClass.getModifiers() & Modifier.STATIC) != 0, "module must be static: %s", moduleClass.getName()); } } /** {@link Ticker} implementation for mocking without restarting GerritServer */ public static class TestTicker extends Ticker { Ticker actualTicker; public TestTicker() { useDefaultTicker(); } /** Switches to system ticker */ @CanIgnoreReturnValue public Ticker useDefaultTicker() { this.actualTicker = Ticker.systemTicker(); return actualTicker; } /** Switches to {@link FakeTicker} */ @CanIgnoreReturnValue public FakeTicker useFakeTicker() { if (!(this.actualTicker instanceof FakeTicker)) { this.actualTicker = new FakeTicker(); } return (FakeTicker) actualTicker; } @Override public long read() { return actualTicker.read(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy