blob: 99db40a0be0999013daa26648aef7ca07e282aa4 [file] [log] [blame]
// 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.Truth.assertWithMessage;
import static com.google.gerrit.acceptance.GitUtil.pushHead;
import static org.junit.Assert.assertEquals;
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.Sets;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.UsedAt;
import com.google.gerrit.common.UsedAt.Project;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.server.approval.ApprovalsUtil;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ReviewerStateInternal;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jgit.api.TagCommand;
import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.revwalk.RevBlob;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.PushResult;
import org.eclipse.jgit.transport.RemoteRefUpdate;
import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
public class PushOneCommit {
public static final String SUBJECT = "test commit";
public static final String FILE_NAME = "a.txt";
public static final String FILE_CONTENT = "some content";
public static final String PATCH_FILE_ONLY =
"diff --git a/a.txt b/a.txt\n"
+ "new file mode 100644\n"
+ "index 0000000..f0eec86\n"
+ "--- /dev/null\n"
+ "+++ b/a.txt\n"
+ "@@ -0,0 +1 @@\n"
+ "+some content\n"
+ "\\ No newline at end of file\n";
public static final String PATCH =
"From %s Mon Sep 17 00:00:00 2001\n"
+ "From: Administrator <admin@example.com>\n"
+ "Date: %s\n"
+ "Subject: [PATCH] test commit\n"
+ "\n"
+ "Change-Id: %s\n"
+ "---\n"
+ "\n"
+ PATCH_FILE_ONLY;
public interface Factory {
PushOneCommit create(PersonIdent i, TestRepository<?> testRepo);
PushOneCommit create(
PersonIdent i, TestRepository<?> testRepo, @Assisted("changeId") String changeId);
PushOneCommit create(
PersonIdent i,
TestRepository<?> testRepo,
@Assisted("subject") String subject,
@Assisted("fileName") String fileName,
@Assisted("content") String content);
PushOneCommit create(
PersonIdent i,
TestRepository<?> testRepo,
@Assisted String subject,
@Assisted Map<String, String> files);
@UsedAt(Project.PLUGIN_CODE_OWNERS)
PushOneCommit create(
PersonIdent i,
TestRepository<?> testRepo,
@Assisted("subject") String subject,
@Assisted Map<String, String> files,
@Assisted("changeId") String changeId);
PushOneCommit create(
PersonIdent i,
TestRepository<?> testRepo,
@Assisted("subject") String subject,
@Assisted("fileName") String fileName,
@Assisted("content") String content,
@Assisted("changeId") String changeId);
}
public static class Tag {
public String name;
public Tag(String name) {
this.name = name;
}
}
public static class AnnotatedTag extends Tag {
public String message;
public PersonIdent tagger;
public AnnotatedTag(String name, String message, PersonIdent tagger) {
super(name);
this.message = message;
this.tagger = tagger;
}
}
private static final AtomicInteger CHANGE_ID_COUNTER = new AtomicInteger();
private static String nextChangeId() {
// Tests use a variety of mechanisms for setting temporary timestamps, so we can't guarantee
// that the PersonIdent (or any other field used by the Change-Id generator) for any two test
// methods in the same acceptance test class are going to be different. But tests generally
// assume that Change-Ids are unique unless otherwise specified. So, don't even bother trying to
// reuse JGit's Change-Id generator, just do the simplest possible thing and convert a counter
// to hex.
return String.format("%040x", CHANGE_ID_COUNTER.incrementAndGet());
}
private final ChangeNotes.Factory notesFactory;
private final ApprovalsUtil approvalsUtil;
private final Provider<InternalChangeQuery> queryProvider;
private final TestRepository<?> testRepo;
private final String subject;
private final Map<String, String> files;
private String changeId;
private Tag tag;
private boolean force;
private List<String> pushOptions;
private final TestRepository<?>.CommitBuilder commitBuilder;
@AssistedInject
PushOneCommit(
ChangeNotes.Factory notesFactory,
ApprovalsUtil approvalsUtil,
Provider<InternalChangeQuery> queryProvider,
@Assisted PersonIdent i,
@Assisted TestRepository<?> testRepo)
throws Exception {
this(notesFactory, approvalsUtil, queryProvider, i, testRepo, SUBJECT, FILE_NAME, FILE_CONTENT);
}
@AssistedInject
PushOneCommit(
ChangeNotes.Factory notesFactory,
ApprovalsUtil approvalsUtil,
Provider<InternalChangeQuery> queryProvider,
@Assisted PersonIdent i,
@Assisted TestRepository<?> testRepo,
@Assisted("changeId") String changeId)
throws Exception {
this(
notesFactory,
approvalsUtil,
queryProvider,
i,
testRepo,
SUBJECT,
FILE_NAME,
FILE_CONTENT,
changeId);
}
@AssistedInject
PushOneCommit(
ChangeNotes.Factory notesFactory,
ApprovalsUtil approvalsUtil,
Provider<InternalChangeQuery> queryProvider,
@Assisted PersonIdent i,
@Assisted TestRepository<?> testRepo,
@Assisted("subject") String subject,
@Assisted("fileName") String fileName,
@Assisted("content") String content)
throws Exception {
this(notesFactory, approvalsUtil, queryProvider, i, testRepo, subject, fileName, content, null);
}
@AssistedInject
PushOneCommit(
ChangeNotes.Factory notesFactory,
ApprovalsUtil approvalsUtil,
Provider<InternalChangeQuery> queryProvider,
@Assisted PersonIdent i,
@Assisted TestRepository<?> testRepo,
@Assisted String subject,
@Assisted Map<String, String> files)
throws Exception {
this(notesFactory, approvalsUtil, queryProvider, i, testRepo, subject, files, null);
}
@AssistedInject
PushOneCommit(
ChangeNotes.Factory notesFactory,
ApprovalsUtil approvalsUtil,
Provider<InternalChangeQuery> queryProvider,
@Assisted PersonIdent i,
@Assisted TestRepository<?> testRepo,
@Assisted("subject") String subject,
@Assisted("fileName") String fileName,
@Assisted("content") String content,
@Nullable @Assisted("changeId") String changeId)
throws Exception {
this(
notesFactory,
approvalsUtil,
queryProvider,
i,
testRepo,
subject,
ImmutableMap.of(fileName, content),
changeId);
}
@AssistedInject
PushOneCommit(
ChangeNotes.Factory notesFactory,
ApprovalsUtil approvalsUtil,
Provider<InternalChangeQuery> queryProvider,
@Assisted PersonIdent i,
@Assisted TestRepository<?> testRepo,
@Assisted("subject") String subject,
@Assisted Map<String, String> files,
@Nullable @Assisted("changeId") String changeId)
throws Exception {
this.testRepo = testRepo;
this.notesFactory = notesFactory;
this.approvalsUtil = approvalsUtil;
this.queryProvider = queryProvider;
this.subject = subject;
this.files = files;
this.changeId = changeId;
if (changeId != null) {
commitBuilder = testRepo.amendRef("HEAD").insertChangeId(changeId.substring(1));
} else {
if (subject.contains("\nChange-Id: ")) {
commitBuilder = testRepo.amendRef("HEAD");
} else {
commitBuilder = testRepo.branch("HEAD").commit().insertChangeId(nextChangeId());
}
}
commitBuilder.message(subject).author(i).committer(new PersonIdent(i, testRepo.getDate()));
}
public PushOneCommit setParents(List<RevCommit> parents) throws Exception {
commitBuilder.noParents();
for (RevCommit p : parents) {
commitBuilder.parent(p);
}
return this;
}
public PushOneCommit setParent(RevCommit parent) throws Exception {
commitBuilder.noParents();
commitBuilder.parent(parent);
return this;
}
public PushOneCommit noParent() throws Exception {
commitBuilder.noParents();
return this;
}
public PushOneCommit addSymlink(String path, String target) throws Exception {
RevBlob blobId = testRepo.blob(target);
commitBuilder.edit(
new PathEdit(path) {
@Override
public void apply(DirCacheEntry ent) {
ent.setFileMode(FileMode.SYMLINK);
ent.setObjectId(blobId);
}
});
return this;
}
public PushOneCommit addGitSubmodule(String modulePath, ObjectId commitId) {
commitBuilder.edit(
new PathEdit(modulePath) {
@Override
public void apply(DirCacheEntry ent) {
ent.setFileMode(FileMode.GITLINK);
ent.setObjectId(commitId);
}
});
return this;
}
public PushOneCommit rmFile(String filename) {
commitBuilder.rm(filename);
return this;
}
public Result to(String ref) throws Exception {
for (Map.Entry<String, String> e : files.entrySet()) {
commitBuilder.add(e.getKey(), e.getValue());
}
return execute(ref);
}
public Result rm(String ref) throws Exception {
for (String fileName : files.keySet()) {
commitBuilder.rm(fileName);
}
return execute(ref);
}
public Result execute(String ref) throws Exception {
RevCommit c = commitBuilder.create();
if (changeId == null) {
changeId = GitUtil.getChangeId(testRepo, c).get();
}
if (tag != null) {
TagCommand tagCommand = testRepo.git().tag().setName(tag.name);
if (tag instanceof AnnotatedTag) {
AnnotatedTag annotatedTag = (AnnotatedTag) tag;
tagCommand
.setAnnotated(true)
.setMessage(annotatedTag.message)
.setTagger(annotatedTag.tagger);
} else {
tagCommand.setAnnotated(false);
}
tagCommand.call();
}
return new Result(ref, pushHead(testRepo, ref, tag != null, force, pushOptions), c, subject);
}
public void setTag(Tag tag) {
this.tag = tag;
}
public void setForce(boolean force) {
this.force = force;
}
public List<String> getPushOptions() {
return pushOptions;
}
public void setPushOptions(List<String> pushOptions) {
this.pushOptions = pushOptions;
}
public void noParents() {
commitBuilder.noParents();
}
public class Result {
private final String ref;
private final PushResult result;
private final RevCommit commit;
private final String resSubj;
private Result(String ref, PushResult resSubj, RevCommit commit, String subject) {
this.ref = ref;
this.result = resSubj;
this.commit = commit;
this.resSubj = subject;
}
public ChangeData getChange() {
return Iterables.getOnlyElement(queryProvider.get().byKeyPrefix(changeId));
}
public PatchSet getPatchSet() {
return getChange().currentPatchSet();
}
public PatchSet.Id getPatchSetId() {
return getChange().change().currentPatchSetId();
}
public String getChangeId() {
return changeId;
}
public RevCommit getCommit() {
return commit;
}
public void assertPushOptions(List<String> pushOptions) {
assertEquals(pushOptions, getPushOptions());
}
public void assertChange(
Change.Status expectedStatus, String expectedTopic, TestAccount... expectedReviewers) {
assertChange(
expectedStatus, expectedTopic, Arrays.asList(expectedReviewers), ImmutableList.of());
}
public void assertChange(
Change.Status expectedStatus,
String expectedTopic,
List<TestAccount> expectedReviewers,
List<TestAccount> expectedCcs) {
Change c = getChange().change();
assertThat(c.getSubject()).isEqualTo(resSubj);
assertThat(c.getStatus()).isEqualTo(expectedStatus);
assertThat(Strings.emptyToNull(c.getTopic())).isEqualTo(expectedTopic);
assertReviewers(c, ReviewerStateInternal.REVIEWER, expectedReviewers);
assertReviewers(c, ReviewerStateInternal.CC, expectedCcs);
}
private void assertReviewers(
Change c, ReviewerStateInternal state, List<TestAccount> expectedReviewers) {
Iterable<Account.Id> actualIds =
approvalsUtil.getReviewers(notesFactory.createChecked(c)).byState(state);
assertThat(actualIds)
.containsExactlyElementsIn(Sets.newHashSet(TestAccount.ids(expectedReviewers)));
}
public void assertOkStatus() {
assertStatus(Status.OK, null);
}
public void assertErrorStatus(String expectedMessage) {
assertStatus(Status.REJECTED_OTHER_REASON, expectedMessage);
}
public void assertErrorStatus() {
RemoteRefUpdate refUpdate = result.getRemoteUpdate(ref);
assertThat(refUpdate).isNotNull();
assertWithMessage(message(refUpdate))
.that(refUpdate.getStatus())
.isEqualTo(Status.REJECTED_OTHER_REASON);
}
private void assertStatus(Status expectedStatus, String expectedMessage) {
RemoteRefUpdate refUpdate = result.getRemoteUpdate(ref);
assertThat(refUpdate).isNotNull();
assertWithMessage(message(refUpdate)).that(refUpdate.getStatus()).isEqualTo(expectedStatus);
if (expectedMessage == null) {
assertThat(refUpdate.getMessage()).isNull();
} else {
assertThat(refUpdate.getMessage()).contains(expectedMessage);
}
}
public void assertMessage(String expectedMessage) {
RemoteRefUpdate refUpdate = result.getRemoteUpdate(ref);
assertThat(refUpdate).isNotNull();
assertThat(message(refUpdate).toLowerCase()).contains(expectedMessage.toLowerCase());
}
public void assertNotMessage(String message) {
RemoteRefUpdate refUpdate = result.getRemoteUpdate(ref);
assertThat(message(refUpdate).toLowerCase()).doesNotContain(message.toLowerCase());
}
public String getMessage() {
RemoteRefUpdate refUpdate = result.getRemoteUpdate(ref);
assertThat(refUpdate).isNotNull();
return message(refUpdate);
}
private String message(RemoteRefUpdate refUpdate) {
StringBuilder b = new StringBuilder();
if (refUpdate.getMessage() != null) {
b.append(refUpdate.getMessage());
b.append("\n");
}
b.append(result.getMessages());
return b.toString();
}
}
}