| // 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.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 org.eclipse.jgit.lib.Constants.HEAD; |
| |
| 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.collect.Iterators; |
| import com.google.common.collect.Sets; |
| import com.google.common.primitives.Chars; |
| import com.google.gerrit.acceptance.AcceptanceTestRequestScope.Context; |
| 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.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.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.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.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.git.GitRepositoryManager; |
| import com.google.gerrit.server.git.MetaDataUpdate; |
| import com.google.gerrit.server.git.ProjectConfig; |
| 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.send.EmailHeader; |
| import com.google.gerrit.server.notedb.ChangeNoteUtil; |
| import com.google.gerrit.server.notedb.ChangeNotes; |
| import com.google.gerrit.server.project.ChangeControl; |
| 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.testutil.ConfigSuite; |
| import com.google.gerrit.testutil.FakeEmailSender; |
| import com.google.gerrit.testutil.FakeEmailSender.Message; |
| import com.google.gerrit.testutil.TempFileUtil; |
| import com.google.gerrit.testutil.TestNotesMigration; |
| 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 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.TemporaryFolder; |
| import org.junit.rules.TestRule; |
| import org.junit.runner.Description; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.model.Statement; |
| |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| 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 java.util.zip.ZipEntry; |
| import java.util.zip.ZipFile; |
| |
| @RunWith(ConfigSuite.class) |
| public abstract class AbstractDaemonTest { |
| private static GerritServer commonServer; |
| |
| @ConfigSuite.Parameter |
| public Config baseConfig; |
| |
| @ConfigSuite.Name |
| private String configName; |
| |
| @Inject |
| protected AllProjectsName allProjects; |
| |
| @Inject |
| protected AccountCreator accounts; |
| |
| @Inject |
| private SchemaFactory<ReviewDb> reviewDbProvider; |
| |
| @Inject |
| protected GerritApi gApi; |
| |
| @Inject |
| protected AcceptanceTestRequestScope atrScope; |
| |
| @Inject |
| protected AccountCache accountCache; |
| |
| @Inject |
| protected IdentifiedUser.GenericFactory identifiedUserFactory; |
| |
| @Inject |
| protected PushOneCommit.Factory pushFactory; |
| |
| @Inject |
| protected MetaDataUpdate.Server metaDataUpdateFactory; |
| |
| @Inject |
| protected ProjectCache projectCache; |
| |
| @Inject |
| protected GroupCache groupCache; |
| |
| @Inject |
| protected GitRepositoryManager repoManager; |
| |
| @Inject |
| protected ChangeIndexer indexer; |
| |
| @Inject |
| protected Provider<InternalChangeQuery> queryProvider; |
| |
| @Inject |
| @CanonicalWebUrl |
| protected Provider<String> canonicalWebUrl; |
| |
| @Inject |
| @GerritServerConfig |
| protected Config cfg; |
| |
| @Inject |
| private InProcessProtocol inProcessProtocol; |
| |
| @Inject |
| private Provider<AnonymousUser> anonymousUser; |
| |
| @Inject |
| @GerritPersonIdent |
| protected Provider<PersonIdent> serverIdent; |
| |
| @Inject |
| protected ChangeData.Factory changeDataFactory; |
| |
| @Inject |
| protected PatchSetUtil psUtil; |
| |
| @Inject |
| protected ChangeFinder changeFinder; |
| |
| @Inject |
| protected Revisions revisions; |
| |
| @Inject |
| protected FakeEmailSender sender; |
| |
| @Inject |
| protected ChangeNoteUtil changeNoteUtil; |
| |
| @Inject |
| protected ChangeResource.Factory changeResourceFactory; |
| |
| @Inject |
| private EventRecorder.Factory eventRecorderFactory; |
| |
| @Inject |
| private ChangeIndexCollection changeIndexes; |
| |
| protected TestRepository<InMemoryRepository> testRepo; |
| protected GerritServer server; |
| protected TestAccount admin; |
| protected TestAccount user; |
| protected RestSession adminRestSession; |
| protected RestSession userRestSession; |
| protected SshSession adminSshSession; |
| protected SshSession userSshSession; |
| protected ReviewDb db; |
| protected Project.NameKey project; |
| protected EventRecorder eventRecorder; |
| |
| @Inject |
| protected TestNotesMigration notesMigration; |
| |
| @Inject |
| protected ChangeNotes.Factory notesFactory; |
| |
| @Inject |
| protected Abandon changeAbandoner; |
| |
| @Rule |
| public ExpectedException exception = ExpectedException.none(); |
| |
| private String resourcePrefix; |
| private List<Repository> toClose; |
| |
| @Rule |
| public TestRule testRunner = new TestRule() { |
| @Override |
| public Statement apply(final Statement base, final Description description) { |
| return new Statement() { |
| @Override |
| public void evaluate() throws Throwable { |
| beforeTest(description); |
| try { |
| base.evaluate(); |
| } finally { |
| afterTest(); |
| } |
| } |
| }; |
| } |
| }; |
| |
| @Rule |
| public TemporaryFolder tempSiteDir = new TemporaryFolder(); |
| |
| @Before |
| public void clearSender() { |
| sender.clear(); |
| } |
| |
| @Before |
| public void startEventRecorder() { |
| eventRecorder = eventRecorderFactory.create(admin); |
| } |
| |
| @After |
| public void closeEventRecorder() { |
| eventRecorder.close(); |
| } |
| |
| @AfterClass |
| public static void stopCommonServer() throws Exception { |
| if (commonServer != null) { |
| try { |
| commonServer.stop(); |
| } finally { |
| commonServer = null; |
| } |
| } |
| TempFileUtil.cleanup(); |
| } |
| |
| protected static Config submitWholeTopicEnabledConfig() { |
| Config cfg = new Config(); |
| cfg.setBoolean("change", null, "submitWholeTopic", true); |
| return cfg; |
| } |
| |
| protected static Config allowDraftsDisabledConfig() { |
| Config cfg = new Config(); |
| cfg.setBoolean("change", null, "allowDrafts", false); |
| return cfg; |
| } |
| |
| protected boolean isAllowDrafts() { |
| return cfg.getBoolean("change", "allowDrafts", true); |
| } |
| |
| 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 { |
| GerritServer.Description classDesc = |
| GerritServer.Description.forTestClass(description, configName); |
| GerritServer.Description methodDesc = |
| GerritServer.Description.forTestMethod(description, configName); |
| |
| baseConfig.setString("gerrit", null, "tempSiteDir", |
| tempSiteDir.getRoot().getPath()); |
| baseConfig.setInt("receive", null, "changeUpdateThreads", 4); |
| if (classDesc.equals(methodDesc) && !classDesc.sandboxed() && |
| !methodDesc.sandboxed()) { |
| if (commonServer == null) { |
| commonServer = GerritServer.start(classDesc, baseConfig); |
| } |
| server = commonServer; |
| } else { |
| server = GerritServer.start(methodDesc, baseConfig); |
| } |
| |
| server.getTestInjector().injectMembers(this); |
| notesMigration.setFromEnv(); |
| Transport.register(inProcessProtocol); |
| toClose = Collections.synchronizedList(new ArrayList<Repository>()); |
| admin = accounts.admin(); |
| user = accounts.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); |
| initSsh(admin); |
| db = reviewDbProvider.open(); |
| 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(""); |
| |
| project = createProject(projectInput(description)); |
| testRepo = cloneProject(project, getCloneAsAccount(description)); |
| } |
| |
| private TestAccount getCloneAsAccount(Description description) { |
| TestProjectInput ann = description.getAnnotation(TestProjectInput.class); |
| return accounts.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(); |
| } 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. |
| * <p> |
| * 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<InMemoryRepository> cloneProject(Project.NameKey p) |
| throws Exception { |
| return cloneProject(p, admin); |
| } |
| |
| protected TestRepository<InMemoryRepository> cloneProject(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 GitUtil.cloneProject( |
| p, inProcessProtocol.register(ctx, repo).toString()); |
| } |
| |
| private void afterTest() throws Exception { |
| Transport.unregister(inProcessProtocol); |
| for (Repository repo : toClose) { |
| repo.close(); |
| } |
| db.close(); |
| adminSshSession.close(); |
| userSshSession.close(); |
| if (server != commonServer) { |
| server.stop(); |
| } |
| } |
| |
| 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<String> 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 createDraftChange() throws Exception { |
| return pushTo("refs/drafts/master"); |
| } |
| |
| 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<Character> 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); |
| PushOneCommit push = |
| pushFactory.create(db, testAccount.getIdent(), repo, |
| PushOneCommit.SUBJECT, PushOneCommit.FILE_NAME, |
| new String(Chars.toArray(RANDOM)), changeId); |
| return push.to(ref); |
| } |
| |
| protected void merge(PushOneCommit.Result r) throws Exception { |
| revision(r).review(ReviewInput.approve()); |
| revision(r).submit(); |
| } |
| |
| protected PushOneCommit.Result amendChangeAsDraft(String changeId) |
| throws Exception { |
| return amendChange(changeId, "refs/drafts/master"); |
| } |
| |
| 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 EditInfo getEdit(String id) |
| throws RestApiException { |
| return gApi.changes().id(id).getEdit(); |
| } |
| |
| protected ChangeInfo get(String id, ListChangesOption... options) |
| throws RestApiException { |
| return gApi.changes().id(id).get( |
| Sets.newEnumSet(Arrays.asList(options), ListChangesOption.class)); |
| } |
| |
| protected List<ChangeInfo> 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())); |
| } |
| |
| 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.setFailOnLoad(true); |
| return atrScope.disableDb(); |
| } |
| |
| protected void enableDb(Context preDisableContext) { |
| notesMigration.setFailOnLoad(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 permission, AccountGroup.UUID id, String ref) |
| throws Exception { |
| ProjectConfig cfg = projectCache.checkedGet(project).getConfig(); |
| Util.allow(cfg, permission, id, ref); |
| saveProjectConfig(project, cfg); |
| } |
| |
| protected void allowGlobalCapabilities(AccountGroup.UUID id, |
| String... capabilityNames) throws Exception { |
| allowGlobalCapabilities(id, Arrays.asList(capabilityNames)); |
| } |
| |
| protected void allowGlobalCapabilities(AccountGroup.UUID id, |
| Iterable<String> 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<String> 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 deny(String permission, AccountGroup.UUID id, String ref) |
| throws Exception { |
| deny(project, permission, id, ref); |
| } |
| |
| protected void deny(Project.NameKey p, String permission, |
| AccountGroup.UUID id, String ref) throws Exception { |
| ProjectConfig cfg = projectCache.checkedGet(p).getConfig(); |
| Util.deny(cfg, permission, id, ref); |
| saveProjectConfig(p, cfg); |
| } |
| |
| protected PermissionRule block(String permission, AccountGroup.UUID id, String ref) |
| throws Exception { |
| return block(permission, id, ref, project); |
| } |
| |
| protected PermissionRule block(String permission, |
| AccountGroup.UUID id, String ref, Project.NameKey project) |
| throws Exception { |
| ProjectConfig cfg = projectCache.checkedGet(project).getConfig(); |
| PermissionRule rule = Util.block(cfg, permission, id, ref); |
| saveProjectConfig(project, cfg); |
| return rule; |
| } |
| |
| 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(String permission, Project.NameKey project, String ref) |
| throws RepositoryNotFoundException, IOException, ConfigInvalidException { |
| grant(permission, project, ref, false); |
| } |
| |
| protected void grant(String permission, Project.NameKey project, String ref, |
| boolean force) throws RepositoryNotFoundException, IOException, |
| ConfigInvalidException { |
| AccountGroup adminGroup = |
| groupCache.get(new AccountGroup.NameKey("Administrators")); |
| grant(permission, project, ref, force, adminGroup.getGroupUUID()); |
| } |
| |
| protected void grant(String permission, Project.NameKey project, String ref, |
| 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 removePermission(String permission, Project.NameKey project, |
| String ref) 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(Permission.READ, REGISTERED_USERS, ref); |
| } |
| |
| 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 Map<String, ActionInfo> getActions(String id) throws Exception { |
| return gApi.changes() |
| .id(id) |
| .revision(1) |
| .actions(); |
| } |
| |
| private static Iterable<String> changeIds(Iterable<ChangeInfo> changes) { |
| return Iterables.transform(changes, i -> i.changeId); |
| } |
| |
| protected void assertSubmittedTogether(String chId, String... expected) |
| throws Exception { |
| List<ChangeInfo> 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<ChangeControl> ctls = changeFinder.find( |
| changeId, atrScope.get().getUser()); |
| assertThat(ctls).hasSize(1); |
| return changeResourceFactory.create(ctls.get(0)); |
| } |
| |
| protected String createGroup(String name) throws Exception { |
| return createGroup(name, "Administrators"); |
| } |
| |
| 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 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 assertMailFrom(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()).isEqualTo(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"); |
| AccountGroup caGroup = groupCache.get( |
| new AccountGroup.UUID(groupApi.detail().id)); |
| GroupReference groupRef = GroupReference.forGroup(caGroup); |
| 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; |
| } |
| |
| /** |
| * Fetches each bundle into a newly cloned repository, then it applies |
| * the bundle, and returns the resulting tree id. |
| */ |
| protected Map<Branch.NameKey, RevTree> |
| fetchFromBundles(BinaryResult bundles) throws Exception { |
| |
| assertThat(bundles.getContentType()).isEqualTo("application/x-zip"); |
| |
| File tempfile = File.createTempFile("test", null); |
| bundles.writeTo(new FileOutputStream(tempfile)); |
| |
| Map<Branch.NameKey, RevTree> ret = new HashMap<>(); |
| try (ZipFile readback = new ZipFile(tempfile);) { |
| for (ZipEntry entry : ImmutableList.copyOf( |
| Iterators.forEnumeration(readback.entries()))) { |
| String bundleName = entry.getName(); |
| InputStream bundleStream = readback.getInputStream(entry); |
| |
| 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 (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 branchName = r.getName(); |
| Branch.NameKey n = new Branch.NameKey(proj, branchName); |
| |
| RevCommit c = localRepo.getRevWalk().parseCommit(r.getObjectId()); |
| ret.put(n, c.getTree()); |
| } |
| } |
| } |
| } |
| return ret; |
| } |
| |
| /** |
| * Assert that the given branches have the given tree ids. |
| */ |
| protected void assertRevTrees(Project.NameKey proj, |
| Map<Branch.NameKey, RevTree> trees) throws Exception { |
| TestRepository<?> localRepo = cloneProject(proj); |
| GitUtil.fetch(localRepo, "refs/*:refs/*"); |
| Map<String, Ref> refs = localRepo.getRepository().getAllRefs(); |
| Map<Branch.NameKey, RevTree> 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<String> 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(); |
| } |
| } |