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

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

// 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.truth.Truth.assertThat;
import static com.google.common.truth.TruthJUnit.assume;
import static com.google.gerrit.acceptance.GitUtil.initSsh;
import static com.google.gerrit.extensions.api.changes.SubmittedTogetherOption.NON_VISIBLE_CHANGES;
import static com.google.gerrit.reviewdb.client.Patch.COMMIT_MSG;
import static com.google.gerrit.reviewdb.client.Patch.MERGE_LIST;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static com.google.gerrit.server.project.Util.category;
import static com.google.gerrit.server.project.Util.value;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.toList;
import static org.eclipse.jgit.lib.Constants.HEAD;
import static org.eclipse.jgit.lib.Constants.R_TAGS;

import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.jimfs.Jimfs;
import com.google.common.primitives.Chars;
import com.google.gerrit.acceptance.AcceptanceTestRequestScope.Context;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.ContributorAgreement;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.LabelFunction;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelValue;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.extensions.api.GerritApi;
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.groups.GroupApi;
import com.google.gerrit.extensions.api.groups.GroupInput;
import com.google.gerrit.extensions.api.projects.BranchApi;
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.SubmitType;
import com.google.gerrit.extensions.common.ActionInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeType;
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.RestApiException;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.ChangeFinder;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.OutputFormat;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.Accounts;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.change.Abandon;
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.change.Revisions;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.PluginConfigFactory;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.group.Groups;
import com.google.gerrit.server.group.InternalGroup;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.index.change.ChangeIndex;
import com.google.gerrit.server.index.change.ChangeIndexCollection;
import com.google.gerrit.server.index.change.ChangeIndexer;
import com.google.gerrit.server.mail.Address;
import com.google.gerrit.server.mail.send.EmailHeader;
import com.google.gerrit.server.notedb.ChangeNoteUtil;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.MutableNotesMigration;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.Util;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.testutil.ConfigSuite;
import com.google.gerrit.testutil.FakeEmailSender;
import com.google.gerrit.testutil.FakeEmailSender.Message;
import com.google.gerrit.testutil.NoteDbMode;
import com.google.gerrit.testutil.SshMode;
import com.google.gerrit.testutil.TempFileUtil;
import com.google.gson.Gson;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
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.NullProgressMonitor;
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.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.FetchResult;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.Transport;
import org.eclipse.jgit.transport.TransportBundleStream;
import org.eclipse.jgit.transport.URIish;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.ExpectedException;
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 {
  private static GerritServer commonServer;
  private static Description firstTest;

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

  @Rule public ExpectedException exception = ExpectedException.none();

  @Rule
  public TestRule testRunner =
      new TestRule() {
        @Override
        public Statement apply(Statement base, Description description) {
          return new Statement() {
            @Override
            public void evaluate() throws Throwable {
              if (firstTest == null) {
                firstTest = description;
              }
              beforeTest(description);
              try {
                base.evaluate();
              } finally {
                afterTest();
              }
            }
          };
        }
      };

  @Inject @CanonicalWebUrl protected Provider canonicalWebUrl;
  @Inject @GerritPersonIdent protected Provider serverIdent;
  @Inject @GerritServerConfig protected Config cfg;
  @Inject protected AcceptanceTestRequestScope atrScope;
  @Inject protected AccountCache accountCache;
  @Inject protected AccountCreator accountCreator;
  @Inject protected Accounts accounts;
  @Inject protected AllProjectsName allProjects;
  @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 GroupCache groupCache;
  @Inject protected IdentifiedUser.GenericFactory identifiedUserFactory;
  @Inject protected MetaDataUpdate.Server metaDataUpdateFactory;
  @Inject protected PatchSetUtil psUtil;
  @Inject protected ProjectCache projectCache;
  @Inject protected Provider queryProvider;
  @Inject protected PushOneCommit.Factory pushFactory;
  @Inject protected PluginConfigFactory pluginConfig;
  @Inject protected Revisions revisions;
  @Inject protected SystemGroupBackend systemGroupBackend;
  @Inject protected MutableNotesMigration notesMigration;
  @Inject protected ChangeNotes.Factory notesFactory;
  @Inject protected Abandon changeAbandoner;

  protected EventRecorder eventRecorder;
  protected GerritServer server;
  protected Project.NameKey project;
  protected RestSession adminRestSession;
  protected RestSession userRestSession;
  protected ReviewDb db;
  protected SshSession adminSshSession;
  protected SshSession userSshSession;
  protected TestAccount admin;
  protected TestAccount user;
  protected TestRepository testRepo;
  protected String resourcePrefix;
  protected Description description;
  protected boolean testRequiresSsh;

  @Inject private ChangeIndexCollection changeIndexes;
  @Inject private EventRecorder.Factory eventRecorderFactory;
  @Inject private InProcessProtocol inProcessProtocol;
  @Inject private Provider anonymousUser;
  @Inject private SchemaFactory reviewDbProvider;
  @Inject private Groups groups;

  private List toClose;

  @Before
  public void clearSender() {
    sender.clear();
  }

  @Before
  public void startEventRecorder() {
    eventRecorder = eventRecorderFactory.create(admin);
  }

  @Before
  public void assumeSshIfRequired() {
    if (testRequiresSsh) {
      // If the test uses ssh, we use assume() to make sure ssh is enabled on
      // the test suite. JUnit will skip tests annotated with @UseSsh if we
      // disable them using the command line flag.
      assume().that(SshMode.useSsh()).isTrue();
    }
  }

  @After
  public void closeEventRecorder() {
    eventRecorder.close();
  }

  @AfterClass
  public static void stopCommonServer() throws Exception {
    if (commonServer != null) {
      try {
        commonServer.close();
      } catch (Throwable t) {
        throw new AssertionError(
            "Error stopping common server in "
                + (firstTest != null ? firstTest.getTestClass().getName() : "unknown test class"),
            t);
      } finally {
        commonServer = null;
      }
    }
    TempFileUtil.cleanup();
  }

  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 {
    this.description = description;
    GerritServer.Description classDesc =
        GerritServer.Description.forTestClass(description, configName);
    GerritServer.Description methodDesc =
        GerritServer.Description.forTestMethod(description, configName);

    baseConfig.setInt("receive", null, "changeUpdateThreads", 4);
    if (classDesc.equals(methodDesc) && !classDesc.sandboxed() && !methodDesc.sandboxed()) {
      if (commonServer == null) {
        commonServer = GerritServer.initAndStart(classDesc, baseConfig);
      }
      server = commonServer;
    } else {
      server = GerritServer.initAndStart(methodDesc, baseConfig);
    }

    server.getTestInjector().injectMembers(this);
    Transport.register(inProcessProtocol);
    toClose = Collections.synchronizedList(new ArrayList());

    db = reviewDbProvider.open();

    // All groups which were added during the server start (e.g. in SchemaCreator) aren't contained
    // in the instance of the group index which is available here and in tests. There are two
    // reasons:
    // 1) No group index is available in SchemaCreator when using an in-memory database. (This could
    // be fixed by using the IndexManagerOnInit in InMemoryDatabase similar as BaseInit uses it.)
    // 2) During the on-init part of the server start, we use another instance of the index than
    // later on. As test indexes are non-permanent, closing an instance and opening another one
    // removes all indexed data.
    // As a workaround, we simply reindex all available groups here.
    Iterable allGroups = groups.getAll(db)::iterator;
    for (AccountGroup group : allGroups) {
      groupCache.evict(group.getGroupUUID(), group.getId(), group.getNameKey());
    }

    admin = accountCreator.admin();
    user = accountCreator.user();

    // Evict cached user state in case tests modify it.
    accountCache.evict(admin.getId());
    accountCache.evict(user.getId());

    adminRestSession = new RestSession(server, admin);
    userRestSession = new RestSession(server, user);

    testRequiresSsh = classDesc.useSshAnnotation() || methodDesc.useSshAnnotation();
    if (testRequiresSsh
        && SshMode.useSsh()
        && (adminSshSession == null || userSshSession == null)) {
      // Create Ssh sessions
      initSsh(admin);
      Context ctx = newRequestContext(user);
      atrScope.set(ctx);
      userSshSession = ctx.getSession();
      userSshSession.open();
      ctx = newRequestContext(admin);
      atrScope.set(ctx);
      adminSshSession = ctx.getSession();
      adminSshSession.open();
    }

    resourcePrefix =
        UNSAFE_PROJECT_NAME
            .matcher(description.getClassName() + "_" + description.getMethodName() + "_")
            .replaceAll("");

    Context ctx = newRequestContext(admin);
    atrScope.set(ctx);
    project = createProject(projectInput(description));
    testRepo = cloneProject(project, getCloneAsAccount(description));
  }

  private TestAccount getCloneAsAccount(Description description) {
    TestProjectInput ann = description.getAnnotation(TestProjectInput.class);
    return accountCreator.get(ann != null ? ann.cloneAs() : "admin");
  }

  private ProjectInput projectInput(Description description) {
    ProjectInput in = new ProjectInput();
    TestProjectInput ann = description.getAnnotation(TestProjectInput.class);
    in.name = name("project");
    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.enableSignedPush = ann.enableSignedPush();
      in.requireSignedPush = ann.requireSignedPush();
    } else {
      // Defaults should match TestProjectConfig, omitting nullable values.
      in.createEmptyCommit = true;
    }
    updateProjectInput(in);
    return in;
  }

  private 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 resourcePrefix + name; } protected Project.NameKey createProject(String nameSuffix) throws RestApiException { return createProject(nameSuffix, null); } protected Project.NameKey createProject(String nameSuffix, Project.NameKey parent) throws RestApiException { // Default for createEmptyCommit should match TestProjectConfig. return createProject(nameSuffix, parent, true, null); } protected Project.NameKey createProject( String nameSuffix, Project.NameKey parent, boolean createEmptyCommit) throws RestApiException { // Default for createEmptyCommit should match TestProjectConfig. return createProject(nameSuffix, parent, createEmptyCommit, null); } protected Project.NameKey createProject( String nameSuffix, Project.NameKey parent, SubmitType submitType) throws RestApiException { // Default for createEmptyCommit should match TestProjectConfig. return createProject(nameSuffix, parent, true, submitType); } protected Project.NameKey createProject( String nameSuffix, Project.NameKey parent, boolean createEmptyCommit, 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; return createProject(in); } private Project.NameKey createProject(ProjectInput in) throws RestApiException { gApi.projects().create(in); return new Project.NameKey(in.name); } /** * 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. } protected TestRepository cloneProject(Project.NameKey p) throws Exception { return cloneProject(p, admin); } protected TestRepository cloneProject( Project.NameKey p, TestAccount testAccount) throws Exception { return GitUtil.cloneProject(p, registerRepoConnection(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. */ protected String registerRepoConnection(Project.NameKey p, TestAccount testAccount) throws Exception { InProcessProtocol.Context ctx = new InProcessProtocol.Context( reviewDbProvider, identifiedUserFactory, testAccount.getId(), 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(); } db.close(); if (adminSshSession != null) { adminSshSession.close(); } if (userSshSession != null) { userSshSession.close(); } if (server != commonServer) { server.close(); server = null; } NoteDbMode.resetFromEnv(notesMigration); } 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; } protected PushOneCommit.Result createChange() throws Exception { return createChange("refs/for/master"); } protected PushOneCommit.Result createChange(String ref) throws Exception { PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo); PushOneCommit.Result result = push.to(ref); result.assertOkStatus(); return result; } protected PushOneCommit.Result createMergeCommitChange(String ref) throws Exception { return createMergeCommitChange(ref, "foo"); } protected PushOneCommit.Result createMergeCommitChange(String ref, String file) throws Exception { ObjectId initial = repo().exactRef(HEAD).getLeaf().getObjectId(); PushOneCommit.Result p1 = pushFactory .create( db, admin.getIdent(), 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( db, admin.getIdent(), testRepo, "parent 2", ImmutableMap.of(file, "foo-2", "bar", "bar-2")) .to(ref); PushOneCommit m = pushFactory.create( db, admin.getIdent(), 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; } protected PushOneCommit.Result createCommitAndPush( TestRepository repo, String ref, String commitMsg, String fileName, String content) throws Exception { PushOneCommit.Result result = pushFactory.create(db, admin.getIdent(), repo, commitMsg, fileName, content).to(ref); result.assertOkStatus(); return result; } protected PushOneCommit.Result createChangeWithTopic() throws Exception { return createChangeWithTopic(testRepo, "topic", "message", "a.txt", "content\n"); } 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/" + name(topic), commitMsg, fileName, content); } protected PushOneCommit.Result createWorkInProgressChange() throws Exception { return pushTo("refs/for/master%wip"); } protected PushOneCommit.Result createChange(String subject, String fileName, String content) throws Exception { PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo, subject, fileName, content); return push.to("refs/for/master"); } protected PushOneCommit.Result createChange( String subject, String fileName, String content, String topic) throws Exception { PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo, subject, fileName, content); return push.to("refs/for/master/" + name(topic)); } protected PushOneCommit.Result createChange( TestRepository repo, String branch, String subject, String fileName, String content, String topic) throws Exception { PushOneCommit push = pushFactory.create(db, admin.getIdent(), repo, subject, fileName, content); return push.to("refs/for/" + branch + "/" + name(topic)); } protected BranchApi createBranch(Branch.NameKey branch) throws Exception { return gApi.projects() .name(branch.getParentKey().get()) .branch(branch.get()) .create(new BranchInput()); } protected BranchApi createBranchWithRevision(Branch.NameKey branch, String revision) throws Exception { BranchInput in = new BranchInput(); in.revision = revision; return gApi.projects().name(branch.getParentKey().get()).branch(branch.get()).create(in); } private static final List RANDOM = Chars.asList(new char[] {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}); protected PushOneCommit.Result amendChange(String changeId) throws Exception { return amendChange(changeId, "refs/for/master"); } protected PushOneCommit.Result amendChange(String changeId, String ref) throws Exception { return amendChange(changeId, ref, admin, testRepo); } 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))); } 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); } protected PushOneCommit.Result amendChange( String changeId, String ref, TestAccount testAccount, TestRepository repo, String subject, String fileName, String content) throws Exception { PushOneCommit push = pushFactory.create(db, testAccount.getIdent(), 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 ChangeInfo get(String id) throws RestApiException { return gApi.changes().id(id).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 List query(String q) throws RestApiException { return gApi.changes().query(q).get(); } private Context newRequestContext(TestAccount account) { return atrScope.newContext( reviewDbProvider, new SshSession(server, account), identifiedUserFactory.create(account.getId())); } /** * Enforce a new request context for the current API user. * *

This recreates the IdentifiedUser, hence everything which is cached in the IdentifiedUser is * reloaded (e.g. the email addresses of the user). */ protected Context resetCurrentApiUser() { return atrScope.set(newRequestContext(atrScope.get().getSession().getAccount())); } protected Context setApiUser(TestAccount account) { return atrScope.set(newRequestContext(account)); } protected Context setApiUserAnonymous() { return atrScope.set(atrScope.newContext(reviewDbProvider, null, anonymousUser.get())); } protected Context disableDb() { notesMigration.setFailOnLoadForTest(true); return atrScope.disableDb(); } protected void enableDb(Context preDisableContext) { notesMigration.setFailOnLoadForTest(false); atrScope.set(preDisableContext); } protected void disableChangeIndexWrites() { for (ChangeIndex i : changeIndexes.getWriteIndexes()) { if (!(i instanceof ReadOnlyChangeIndex)) { changeIndexes.addWriteIndex(new ReadOnlyChangeIndex(i)); } } } protected void enableChangeIndexWrites() { for (ChangeIndex i : changeIndexes.getWriteIndexes()) { if (i instanceof ReadOnlyChangeIndex) { changeIndexes.addWriteIndex(((ReadOnlyChangeIndex) i).unwrap()); } } } 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 allow(String ref, String permission, AccountGroup.UUID id) throws Exception { allow(project, ref, permission, id); } protected void allow(Project.NameKey p, String ref, String permission, AccountGroup.UUID id) throws Exception { ProjectConfig cfg = projectCache.checkedGet(p).getConfig(); Util.allow(cfg, permission, id, ref); saveProjectConfig(p, cfg); } protected void allowGlobalCapabilities(AccountGroup.UUID id, String... capabilityNames) throws Exception { allowGlobalCapabilities(id, Arrays.asList(capabilityNames)); } protected void allowGlobalCapabilities(AccountGroup.UUID id, Iterable capabilityNames) throws Exception { ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig(); for (String capabilityName : capabilityNames) { Util.allow(cfg, capabilityName, id); } saveProjectConfig(allProjects, cfg); } protected void removeGlobalCapabilities(AccountGroup.UUID id, String... capabilityNames) throws Exception { removeGlobalCapabilities(id, Arrays.asList(capabilityNames)); } protected void removeGlobalCapabilities(AccountGroup.UUID id, Iterable capabilityNames) throws Exception { ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig(); for (String capabilityName : capabilityNames) { Util.remove(cfg, capabilityName, id); } saveProjectConfig(allProjects, cfg); } protected void setUseContributorAgreements(InheritableBoolean value) throws Exception { try (MetaDataUpdate md = metaDataUpdateFactory.create(project)) { ProjectConfig config = ProjectConfig.read(md); config.getProject().setUseContributorAgreements(value); config.commit(md); projectCache.evict(config.getProject()); } } protected void setUseSignedOffBy(InheritableBoolean value) throws Exception { try (MetaDataUpdate md = metaDataUpdateFactory.create(project)) { ProjectConfig config = ProjectConfig.read(md); config.getProject().setUseSignedOffBy(value); config.commit(md); projectCache.evict(config.getProject()); } } protected void setRequireChangeId(InheritableBoolean value) throws Exception { try (MetaDataUpdate md = metaDataUpdateFactory.create(project)) { ProjectConfig config = ProjectConfig.read(md); config.getProject().setRequireChangeID(value); config.commit(md); projectCache.evict(config.getProject()); } } protected void deny(String ref, String permission, AccountGroup.UUID id) throws Exception { deny(project, ref, permission, id); } protected void deny(Project.NameKey p, String ref, String permission, AccountGroup.UUID id) throws Exception { ProjectConfig cfg = projectCache.checkedGet(p).getConfig(); Util.deny(cfg, permission, id, ref); saveProjectConfig(p, cfg); } protected PermissionRule block(String ref, String permission, AccountGroup.UUID id) throws Exception { return block(project, ref, permission, id); } protected PermissionRule block( Project.NameKey project, String ref, String permission, AccountGroup.UUID id) throws Exception { ProjectConfig cfg = projectCache.checkedGet(project).getConfig(); PermissionRule rule = Util.block(cfg, permission, id, ref); saveProjectConfig(project, cfg); return rule; } protected void blockLabel( String label, int min, int max, AccountGroup.UUID id, String ref, Project.NameKey project) throws Exception { ProjectConfig cfg = projectCache.checkedGet(project).getConfig(); Util.block(cfg, Permission.LABEL + label, min, max, id, ref); saveProjectConfig(project, cfg); } protected void saveProjectConfig(Project.NameKey p, ProjectConfig cfg) throws Exception { try (MetaDataUpdate md = metaDataUpdateFactory.create(p)) { md.setAuthor(identifiedUserFactory.create(admin.getId())); cfg.commit(md); } projectCache.evict(cfg.getProject()); } protected void saveProjectConfig(ProjectConfig cfg) throws Exception { saveProjectConfig(project, cfg); } protected void grant(Project.NameKey project, String ref, String permission) throws RepositoryNotFoundException, IOException, ConfigInvalidException { grant(project, ref, permission, false); } protected void grant(Project.NameKey project, String ref, String permission, boolean force) throws RepositoryNotFoundException, IOException, ConfigInvalidException { InternalGroup adminGroup = groupCache.get(new AccountGroup.NameKey("Administrators")).orElse(null); grant(project, ref, permission, force, adminGroup.getGroupUUID()); } protected void grant( Project.NameKey project, String ref, String permission, boolean force, AccountGroup.UUID groupUUID) throws RepositoryNotFoundException, IOException, ConfigInvalidException { try (MetaDataUpdate md = metaDataUpdateFactory.create(project)) { md.setMessage(String.format("Grant %s on %s", permission, ref)); ProjectConfig config = ProjectConfig.read(md); AccessSection s = config.getAccessSection(ref, true); Permission p = s.getPermission(permission, true); PermissionRule rule = Util.newRule(config, groupUUID); rule.setForce(force); p.add(rule); config.commit(md); projectCache.evict(config.getProject()); } } protected void grantLabel( String label, int min, int max, Project.NameKey project, String ref, boolean force, AccountGroup.UUID groupUUID, boolean exclusive) throws RepositoryNotFoundException, IOException, ConfigInvalidException { String permission = Permission.LABEL + label; try (MetaDataUpdate md = metaDataUpdateFactory.create(project)) { md.setMessage(String.format("Grant %s on %s", permission, ref)); ProjectConfig config = ProjectConfig.read(md); AccessSection s = config.getAccessSection(ref, true); Permission p = s.getPermission(permission, true); p.setExclusiveGroup(exclusive); PermissionRule rule = Util.newRule(config, groupUUID); rule.setForce(force); rule.setMin(min); rule.setMax(max); p.add(rule); config.commit(md); projectCache.evict(config.getProject()); } } protected void removePermission(Project.NameKey project, String ref, String permission) throws IOException, ConfigInvalidException { try (MetaDataUpdate md = metaDataUpdateFactory.create(project)) { md.setMessage(String.format("Remove %s on %s", permission, ref)); ProjectConfig config = ProjectConfig.read(md); AccessSection s = config.getAccessSection(ref, true); Permission p = s.getPermission(permission, true); p.getRules().clear(); config.commit(md); projectCache.evict(config.getProject()); } } protected void blockRead(String ref) throws Exception { block(ref, Permission.READ, REGISTERED_USERS); } protected void blockForgeCommitter(Project.NameKey project, String ref) throws Exception { ProjectConfig cfg = projectCache.checkedGet(project).getConfig(); Util.block(cfg, Permission.FORGE_COMMITTER, REGISTERED_USERS, ref); saveProjectConfig(project, cfg); } protected PushOneCommit.Result pushTo(String ref) throws Exception { PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo); return push.to(ref); } protected void approve(String id) throws Exception { gApi.changes().id(id).revision("current").review(ReviewInput.approve()); } protected void recommend(String id) throws Exception { gApi.changes().id(id).revision("current").review(ReviewInput.recommend()); } protected Map getActions(String id) throws Exception { return gApi.changes().id(id).revision(1).actions(); } protected String getETag(String id) throws Exception { return gApi.changes().id(id).current().etag(); } private static Iterable changeIds(Iterable changes) { return Iterables.transform(changes, i -> i.changeId); } protected void assertSubmittedTogether(String chId, String... expected) throws Exception { List actual = gApi.changes().id(chId).submittedTogether(); SubmittedTogetherInfo info = gApi.changes().id(chId).submittedTogether(EnumSet.of(NON_VISIBLE_CHANGES)); assertThat(info.nonVisibleChanges).isEqualTo(0); assertThat(actual).hasSize(expected.length); assertThat(changeIds(actual)).containsExactly((Object[]) expected).inOrder(); assertThat(changeIds(info.changes)).containsExactly((Object[]) expected).inOrder(); } protected PatchSet getPatchSet(PatchSet.Id psId) throws OrmException { return changeDataFactory.create(db, project, psId.getParentKey()).patchSet(psId); } protected IdentifiedUser user(TestAccount testAccount) { return identifiedUserFactory.create(testAccount.getId()); } 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.getParentKey().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), atrScope.get().getUser()); } protected String createGroup(String name) throws Exception { return createGroup(name, "Administrators"); } protected String createGroupWithRealName(String name) throws Exception { GroupInput in = new GroupInput(); in.name = name; in.ownerId = "Administrators"; gApi.groups().create(in); return name; } protected String createGroup(String name, String owner) throws Exception { name = name(name); GroupInput in = new GroupInput(); in.name = name; in.ownerId = owner; gApi.groups().create(in); return name; } protected String createAccount(String name, String group) throws Exception { name = name(name); accountCreator.create(name, group); return name; } protected RevCommit getHead(Repository repo, String name) throws Exception { try (RevWalk rw = new RevWalk(repo)) { Ref r = repo.exactRef(name); return r != null ? rw.parseCommit(r.getObjectId()) : null; } } protected RevCommit getHead(Repository repo) throws Exception { return getHead(repo, "HEAD"); } protected RevCommit getRemoteHead(Project.NameKey project, String branch) throws Exception { try (Repository repo = repoManager.openRepository(project)) { return getHead(repo, branch.startsWith(Constants.R_REFS) ? branch : "refs/heads/" + branch); } } protected RevCommit getRemoteHead(String project, String branch) throws Exception { return getRemoteHead(new Project.NameKey(project), branch); } protected RevCommit getRemoteHead() throws Exception { return getRemoteHead(project, "master"); } protected void grantTagPermissions() throws Exception { grant(project, R_TAGS + "*", Permission.CREATE); grant(project, R_TAGS + "", Permission.DELETE); grant(project, R_TAGS + "*", Permission.CREATE_TAG); grant(project, R_TAGS + "*", Permission.CREATE_SIGNED_TAG); } protected void assertMailReplyTo(Message message, String email) throws Exception { assertThat(message.headers()).containsKey("Reply-To"); EmailHeader.String replyTo = (EmailHeader.String) message.headers().get("Reply-To"); assertThat(replyTo.getString()).contains(email); } protected ContributorAgreement configureContributorAgreement(boolean autoVerify) throws Exception { ContributorAgreement ca; if (autoVerify) { String g = createGroup("cla-test-group"); GroupApi groupApi = gApi.groups().id(g); groupApi.description("CLA test group"); InternalGroup caGroup = groupCache.get(new AccountGroup.UUID(groupApi.detail().id)).orElse(null); GroupReference groupRef = new GroupReference(caGroup.getGroupUUID(), caGroup.getName()); PermissionRule rule = new PermissionRule(groupRef); rule.setAction(PermissionRule.Action.ALLOW); ca = new ContributorAgreement("cla-test"); ca.setAutoVerify(groupRef); ca.setAccepted(ImmutableList.of(rule)); } else { ca = new ContributorAgreement("cla-test-no-auto-verify"); } ca.setDescription("description"); ca.setAgreementUrl("agreement-url"); ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig(); cfg.replace(ca); saveProjectConfig(allProjects, cfg); return ca; } protected BinaryResult submitPreview(String changeId) throws Exception { return gApi.changes().id(changeId).current().submitPreview(); } protected BinaryResult submitPreview(String changeId, String format) throws Exception { return gApi.changes().id(changeId).current().submitPreview(format); } protected Map fetchFromSubmitPreview(String changeId) throws Exception { try (BinaryResult result = submitPreview(changeId)) { return fetchFromBundles(result); } } /** * Fetches each bundle into a newly cloned repository, then it applies the bundle, and returns the * resulting tree id. * *

Omits NoteDb meta refs. */ protected Map fetchFromBundles(BinaryResult bundles) throws Exception { assertThat(bundles.getContentType()).isEqualTo("application/x-zip"); FileSystem fs = Jimfs.newFileSystem(); Path previewPath = fs.getPath("preview.zip"); try (OutputStream out = Files.newOutputStream(previewPath)) { bundles.writeTo(out); } Map ret = new HashMap<>(); try (FileSystem zipFs = FileSystems.newFileSystem(previewPath, null); DirectoryStream dirStream = Files.newDirectoryStream(Iterables.getOnlyElement(zipFs.getRootDirectories()))) { for (Path p : dirStream) { if (!Files.isRegularFile(p)) { continue; } String bundleName = p.getFileName().toString(); int len = bundleName.length(); assertThat(bundleName).endsWith(".git"); String repoName = bundleName.substring(0, len - 4); Project.NameKey proj = new Project.NameKey(repoName); TestRepository localRepo = cloneProject(proj); try (InputStream bundleStream = Files.newInputStream(p); TransportBundleStream tbs = new TransportBundleStream( localRepo.getRepository(), new URIish(bundleName), bundleStream)) { FetchResult fr = tbs.fetch( NullProgressMonitor.INSTANCE, Arrays.asList(new RefSpec("refs/*:refs/preview/*"))); for (Ref r : fr.getAdvertisedRefs()) { String refName = r.getName(); if (RefNames.isNoteDbMetaRef(refName)) { continue; } RevCommit c = localRepo.getRevWalk().parseCommit(r.getObjectId()); ret.put(new Branch.NameKey(proj, refName), c.getTree().copy()); } } } } assertThat(ret).isNotEmpty(); return ret; } /** 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 refs = localRepo.getRepository().getAllRefs(); Map refValues = new HashMap<>(); for (Branch.NameKey b : trees.keySet()) { if (!b.getParentKey().equals(proj)) { continue; } Ref r = refs.get(b.get()); 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 assertDiffForNewFile( DiffInfo diff, RevCommit commit, String path, String expectedContentSideB) throws Exception { List expectedLines = new ArrayList<>(); for (String line : expectedContentSideB.split("\n")) { expectedLines.add(line); } assertThat(diff.binary).isNull(); assertThat(diff.changeType).isEqualTo(ChangeType.ADDED); assertThat(diff.diffHeader).isNotNull(); assertThat(diff.intralineStatus).isNull(); assertThat(diff.webLinks).isNull(); assertThat(diff.metaA).isNull(); assertThat(diff.metaB).isNotNull(); assertThat(diff.metaB.commitId).isEqualTo(commit.name()); 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.lines).isEqualTo(expectedLines.size()); assertThat(diff.metaB.name).isEqualTo(path); assertThat(diff.metaB.webLinks).isNull(); assertThat(diff.content).hasSize(1); 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 TestRepository createProjectWithPush( String name, @Nullable Project.NameKey parent, SubmitType submitType) throws Exception { Project.NameKey project = createProject(name, parent, true, submitType); grant(project, "refs/heads/*", Permission.PUSH); grant(project, "refs/for/refs/heads/*", Permission.SUBMIT); return cloneProject(project); } 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 assertNotifyTo(TestAccount expected) { assertNotifyTo(expected.emailAddress); } protected void assertNotifyTo(Address expected) { assertThat(sender.getMessages()).hasSize(1); Message m = sender.getMessages().get(0); assertThat(m.rcpt()).containsExactly(expected); assertThat(((EmailHeader.AddressList) m.headers().get("To")).getAddressList()) .containsExactly(expected); assertThat(m.headers().get("CC").isEmpty()).isTrue(); } protected void assertNotifyCc(TestAccount expected) { assertNotifyCc(expected.emailAddress); } protected void assertNotifyCc(Address expected) { assertThat(sender.getMessages()).hasSize(1); Message m = sender.getMessages().get(0); assertThat(m.rcpt()).containsExactly(expected); assertThat(m.headers().get("To").isEmpty()).isTrue(); assertThat(((EmailHeader.AddressList) m.headers().get("CC")).getAddressList()) .containsExactly(expected); } protected void assertNotifyBcc(TestAccount expected) { assertThat(sender.getMessages()).hasSize(1); Message m = sender.getMessages().get(0); assertThat(m.rcpt()).containsExactly(expected.emailAddress); 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 OrmException, 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.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); } 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).isPresent()).isFalse(); return revCommit; } } protected RevCommit parseCurrentRevision(RevWalk rw, PushOneCommit.Result r) throws Exception { return parseCurrentRevision(rw, r.getChangeId()); } protected RevCommit parseCurrentRevision(RevWalk rw, String changeId) throws Exception { return rw.parseCommit( ObjectId.fromString(get(changeId, ListChangesOption.CURRENT_REVISION).currentRevision)); } protected void enableCreateNewChangeForAllNotInTarget() throws Exception { ProjectConfig config = projectCache.checkedGet(project).getConfig(); config.getProject().setCreateNewChangeForAllNotInTarget(InheritableBoolean.TRUE); saveProjectConfig(project, config); } 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")); } private void configLabel( Project.NameKey project, String label, LabelFunction func, List refPatterns, LabelValue... value) throws Exception { ProjectConfig cfg = projectCache.checkedGet(project).getConfig(); LabelType labelType = category(label, value); labelType.setFunction(func); labelType.setRefPatterns(refPatterns); cfg.getLabelSections().put(labelType.getName(), labelType); saveProjectConfig(project, cfg); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy