Add acceptance test for pushing for review via SSH

Change-Id: I276275509b5f00acc3465cc8787c9fbd1d6ea732
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
This commit is contained in:
Edwin Kempin
2013-02-28 16:30:55 +01:00
parent 69928a6d06
commit 26ea8efb44
6 changed files with 493 additions and 7 deletions

View File

@@ -96,7 +96,7 @@ public class AccountCreator {
accountCache.evictByUsername(username);
byEmailCache.evict(email);
return new TestAccount(username, email, fullName, sshKey, httpPass);
return new TestAccount(id, username, email, fullName, sshKey, httpPass);
} finally {
db.close();
}

View File

@@ -23,6 +23,8 @@ import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import org.eclipse.jgit.lib.PersonIdent;
public class SshSession {
private final TestAccount account;
@@ -65,4 +67,19 @@ public class SshSession {
}
return session;
}
public String getUrl() {
StringBuilder b = new StringBuilder();
b.append("ssh://");
b.append(session.getUserName());
b.append("@");
b.append(session.getHost());
b.append(":");
b.append(session.getPort());
return b.toString();
}
public PersonIdent getIdent() {
return new PersonIdent(session.getUserName(), account.email);
}
}

View File

@@ -0,0 +1,41 @@
// Copyright (C) 2011 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 java.io.File;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
public class TempFileUtil {
private static int testCount;
private static DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
private static final File temp = new File(new File("target"), "temp");
private static String createUniqueTestFolderName() {
return "test_" + (df.format(new Date()) + "_" + (testCount++));
}
public static File createTempDirectory() {
final String name = createUniqueTestFolderName();
final File directory = new File(temp, name);
if (!directory.mkdirs()) {
throw new RuntimeException("failed to create folder '"
+ directory.getAbsolutePath() + "'");
}
return directory;
}
}

View File

@@ -14,20 +14,24 @@
package com.google.gerrit.acceptance;
import com.google.gerrit.reviewdb.client.Account;
import java.io.ByteArrayOutputStream;
import com.jcraft.jsch.KeyPair;
public class TestAccount {
final String username;
final String email;
final String fullName;
final KeyPair sshKey;
final String httpPassword;
public final Account.Id id;
public final String username;
public final String email;
public final String fullName;
public final KeyPair sshKey;
public final String httpPassword;
TestAccount(String username, String email, String fullName,
TestAccount(Account.Id id, String username, String email, String fullName,
KeyPair sshKey, String httpPassword) {
this.id = id;
this.username = username;
this.email = email;
this.fullName = fullName;

View File

@@ -0,0 +1,148 @@
// 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.git.ssh;
import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.SshSession;
import com.google.gerrit.acceptance.TempFileUtil;
import com.google.gerrit.acceptance.TestAccount;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import org.eclipse.jgit.api.AddCommand;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.CommitCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.PushCommand;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.JschConfigSessionFactory;
import org.eclipse.jgit.transport.OpenSshConfig.Host;
import org.eclipse.jgit.transport.PushResult;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.SshSessionFactory;
import org.eclipse.jgit.util.ChangeIdUtil;
import org.eclipse.jgit.util.FS;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
public class GitUtil {
public static void initSsh(final TestAccount a) {
final Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
JSch.setConfig(config);
// register a JschConfigSessionFactory that adds the private key as identity
// to the JSch instance of JGit so that SSH communication via JGit can
// succeed
SshSessionFactory.setInstance(new JschConfigSessionFactory() {
@Override
protected void configure(Host hc, Session session) {
try {
final JSch jsch = getJSch(hc, FS.DETECTED);
jsch.addIdentity("KeyPair", a.privateKey(),
a.sshKey.getPublicKeyBlob(), null);
} catch (JSchException e) {
throw new RuntimeException(e);
}
}
});
}
public static void createProject(SshSession s, String name)
throws JSchException, IOException {
s.exec("gerrit create-project --empty-commit --name \"" + name + "\"");
}
public static Git cloneProject(String url) throws GitAPIException {
final File gitDir = TempFileUtil.createTempDirectory();
final CloneCommand cloneCmd = Git.cloneRepository();
cloneCmd.setURI(url);
cloneCmd.setDirectory(gitDir);
return cloneCmd.call();
}
public static void add(Git git, String path, String content)
throws GitAPIException, IOException {
File f = new File(git.getRepository().getDirectory().getParentFile(), path);
File p = f.getParentFile();
if (!p.exists() && !p.mkdirs()) {
throw new IOException("failed to create dir: " + p.getAbsolutePath());
}
FileWriter w = new FileWriter(f);
BufferedWriter out = new BufferedWriter(w);
try {
out.write(content);
} finally {
out.close();
}
final AddCommand addCmd = git.add();
addCmd.addFilepattern(path);
addCmd.call();
}
public static String createCommit(Git git, PersonIdent i, String msg)
throws GitAPIException, IOException {
return createCommit(git, i, msg, true);
}
public static String createCommit(Git git, PersonIdent i, String msg,
boolean insertChangeId) throws GitAPIException, IOException {
ObjectId changeId = null;
if (insertChangeId) {
changeId = computeChangeId(git, i, msg);
msg = ChangeIdUtil.insertId(msg, changeId);
}
final CommitCommand commitCmd = git.commit();
commitCmd.setAuthor(i);
commitCmd.setCommitter(i);
commitCmd.setMessage(msg);
commitCmd.call();
return changeId != null ? "I" + changeId.getName() : null;
}
private static ObjectId computeChangeId(Git git, PersonIdent i, String msg)
throws IOException {
RevWalk rw = new RevWalk(git.getRepository());
try {
RevCommit parent =
rw.lookupCommit(git.getRepository().getRef(Constants.HEAD).getObjectId());
return ChangeIdUtil.computeChangeId(parent.getTree(), parent.getId(), i, i, msg);
} finally {
rw.release();
}
}
public static PushResult pushHead(Git git, String ref) throws GitAPIException {
PushCommand pushCmd = git.push();
pushCmd.setRefSpecs(new RefSpec("HEAD:" + ref));
Iterable<PushResult> r = pushCmd.call();
return Iterables.getOnlyElement(r);
}
}

View File

@@ -0,0 +1,276 @@
// 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.git.ssh;
import static com.google.gerrit.acceptance.git.ssh.GitUtil.add;
import static com.google.gerrit.acceptance.git.ssh.GitUtil.cloneProject;
import static com.google.gerrit.acceptance.git.ssh.GitUtil.createCommit;
import static com.google.gerrit.acceptance.git.ssh.GitUtil.createProject;
import static com.google.gerrit.acceptance.git.ssh.GitUtil.initSsh;
import static com.google.gerrit.acceptance.git.ssh.GitUtil.pushHead;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import com.google.common.base.Function;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.AccountCreator;
import com.google.gerrit.acceptance.SshSession;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import com.jcraft.jsch.JSchException;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.transport.PushResult;
import org.eclipse.jgit.transport.RemoteRefUpdate;
import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.Arrays;
import java.util.Set;
public class PushForReviewIT extends AbstractDaemonTest {
@Inject
private AccountCreator accounts;
@Inject
private SchemaFactory<ReviewDb> reviewDbProvider;
private TestAccount admin;
private SshSession sshSession;
private Project.NameKey project;
private Git git;
private ReviewDb db;
@Before
public void setUp() throws Exception {
admin =
accounts.create("admin", "admin@example.com", "Administrator",
"Administrators");
sshSession = new SshSession(admin);
project = new Project.NameKey("p");
initSsh(admin);
createProject(sshSession, project.get());
git = cloneProject(sshSession.getUrl() + "/" + project.get());
db = reviewDbProvider.open();
}
@After
public void cleanup() {
sshSession.close();
db.close();
}
@Test
public void testPushForMaster() throws GitAPIException, OrmException,
IOException {
PushOneCommit push = new PushOneCommit();
String ref = "refs/for/master";
PushResult r = push.to(ref);
assertOkStatus(r, ref);
assertChange(push.changeId, Change.Status.NEW, PushOneCommit.SUBJECT, null);
}
@Test
public void testPushForMasterWithTopic() throws GitAPIException,
OrmException, IOException {
// specify topic in ref
PushOneCommit push = new PushOneCommit();
String topic = "my/topic";
String ref = "refs/for/master/" + topic;
PushResult r = push.to(ref);
assertOkStatus(r, ref);
assertChange(push.changeId, Change.Status.NEW, PushOneCommit.SUBJECT, topic);
// specify topic as option
push = new PushOneCommit();
ref = "refs/for/master%topic=" + topic;
r = push.to(ref);
assertOkStatus(r, ref);
assertChange(push.changeId, Change.Status.NEW, PushOneCommit.SUBJECT, topic);
}
@Test
public void testPushForMasterWithCc() throws GitAPIException, OrmException,
IOException, JSchException {
// cc one user
TestAccount user = accounts.create("user", "user@example.com", "User");
PushOneCommit push = new PushOneCommit();
String topic = "my/topic";
String ref = "refs/for/master/" + topic + "%cc=" + user.email;
PushResult r = push.to(ref);
assertOkStatus(r, ref);
assertChange(push.changeId, Change.Status.NEW, PushOneCommit.SUBJECT, topic);
// cc several users
TestAccount user2 =
accounts.create("another-user", "another.user@example.com", "Another User");
push = new PushOneCommit();
ref = "refs/for/master/" + topic + "%cc=" + admin.email + ",cc=" + user.email
+ ",cc=" + user2.email;
r = push.to(ref);
assertOkStatus(r, ref);
assertChange(push.changeId, Change.Status.NEW, PushOneCommit.SUBJECT, topic);
// cc non-existing user
String nonExistingEmail = "non.existing@example.com";
push = new PushOneCommit();
ref = "refs/for/master/" + topic + "%cc=" + admin.email + ",cc="
+ nonExistingEmail + ",cc=" + user.email;
r = push.to(ref);
assertErrorStatus(r, "user \"" + nonExistingEmail + "\" not found", ref);
}
@Test
public void testPushForMasterWithReviewer() throws GitAPIException,
OrmException, IOException, JSchException {
// add one reviewer
TestAccount user = accounts.create("user", "user@example.com", "User");
PushOneCommit push = new PushOneCommit();
String topic = "my/topic";
String ref = "refs/for/master/" + topic + "%r=" + user.email;
PushResult r = push.to(ref);
assertOkStatus(r, ref);
assertChange(push.changeId, Change.Status.NEW, PushOneCommit.SUBJECT,
topic, user);
// add several reviewers
TestAccount user2 =
accounts.create("another-user", "another.user@example.com", "Another User");
push = new PushOneCommit();
ref = "refs/for/master/" + topic + "%r=" + admin.email + ",r=" + user.email
+ ",r=" + user2.email;
r = push.to(ref);
assertOkStatus(r, ref);
// admin is the owner of the change and should not appear as reviewer
assertChange(push.changeId, Change.Status.NEW, PushOneCommit.SUBJECT,
topic, user, user2);
// add non-existing user as reviewer
String nonExistingEmail = "non.existing@example.com";
push = new PushOneCommit();
ref = "refs/for/master/" + topic + "%r=" + admin.email + ",r="
+ nonExistingEmail + ",r=" + user.email;
r = push.to(ref);
assertErrorStatus(r, "user \"" + nonExistingEmail + "\" not found", ref);
}
@Test
public void testPushForMasterAsDraft() throws GitAPIException, OrmException,
IOException {
// create draft by pushing to 'refs/drafts/'
PushOneCommit push = new PushOneCommit();
String ref = "refs/drafts/master";
PushResult r = push.to(ref);
assertOkStatus(r, ref);
assertChange(push.changeId, Change.Status.DRAFT, PushOneCommit.SUBJECT, null);
// create draft by using 'draft' option
push = new PushOneCommit();
ref = "refs/for/master%draft";
r = push.to(ref);
assertOkStatus(r, ref);
assertChange(push.changeId, Change.Status.DRAFT, PushOneCommit.SUBJECT, null);
}
@Test
public void testPushForNonExistingBranch() throws GitAPIException,
OrmException, IOException {
PushOneCommit push = new PushOneCommit();
String branchName = "non-existing";
String ref = "refs/for/" + branchName;
PushResult r = push.to(ref);
assertErrorStatus(r, "branch " + branchName + " not found", ref);
}
private void assertChange(String changeId, Change.Status expectedStatus,
String expectedSubject, String expectedTopic,
TestAccount... expectedReviewers) throws OrmException {
Change c =
Iterables.getOnlyElement(db.changes().byKey(new Change.Key(changeId)).toList());
assertEquals(expectedSubject, c.getSubject());
assertEquals(expectedStatus, c.getStatus());
assertEquals(expectedTopic, Strings.emptyToNull(c.getTopic()));
assertReviewers(c, expectedReviewers);
}
private void assertReviewers(Change c, TestAccount... expectedReviewers)
throws OrmException {
Set<Account.Id> expectedReviewerIds =
Sets.newHashSet(Lists.transform(Arrays.asList(expectedReviewers),
new Function<TestAccount, Account.Id>() {
@Override
public Account.Id apply(TestAccount a) {
return a.id;
}
}));
for (PatchSetApproval psa : db.patchSetApprovals().byPatchSet(
c.currentPatchSetId())) {
assertTrue("unexpected reviewer " + psa.getAccountId(),
expectedReviewerIds.remove(psa.getAccountId()));
}
assertTrue("missing reviewers: " + expectedReviewerIds,
expectedReviewerIds.isEmpty());
}
private static void assertOkStatus(PushResult result, String ref) {
assertStatus(Status.OK, null, result, ref);
}
private static void assertErrorStatus(PushResult result,
String expectedMessage, String ref) {
assertStatus(Status.REJECTED_OTHER_REASON, expectedMessage, result, ref);
}
private static void assertStatus(Status expectedStatus,
String expectedMessage, PushResult result, String ref) {
RemoteRefUpdate refUpdate = result.getRemoteUpdate(ref);
assertEquals(refUpdate.getMessage() + "\n" + result.getMessages(),
expectedStatus, refUpdate.getStatus());
assertEquals(expectedMessage, refUpdate.getMessage());
}
private class PushOneCommit {
final static String FILE_NAME = "a.txt";
final static String FILE_CONTENT = "some content";
final static String SUBJECT = "test commit";
String changeId;
public PushResult to(String ref) throws GitAPIException, IOException {
add(git, FILE_NAME, FILE_CONTENT);
changeId = createCommit(git, sshSession.getIdent(), SUBJECT);
return pushHead(git, ref);
}
}
}