diff options
author | Jukka Jokiniva <jukka.jokiniva@qt.io> | 2019-01-04 11:44:07 +0200 |
---|---|---|
committer | Jukka Jokiniva <jukka.jokiniva@qt.io> | 2019-04-10 06:43:24 +0000 |
commit | 58111cfb36971b3a86963c8d4814ffd151d90143 (patch) | |
tree | 1aa6c7d3e4e9679b9faa17f67a4f3204f8b21b50 | |
parent | bc4a6ad15999b520783630d5489829c82c52411c (diff) |
Add ssh command to approve or fail a build
Fixes: QTBI-1546
Change-Id: I384c2c5c4d236d4b83add92b1cb74886427ec1eb
Reviewed-by: Paul Wicking <paul.wicking@qt.io>
5 files changed, 748 insertions, 0 deletions
diff --git a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandBuildApprove.java b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandBuildApprove.java new file mode 100644 index 0000000..3821966 --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandBuildApprove.java @@ -0,0 +1,326 @@ +// +// Copyright (C) 2019 The Qt Company +// + +package com.googlesource.gerrit.plugins.qtcodereview; + +import com.google.common.flogger.FluentLogger; +import com.google.gerrit.extensions.restapi.AuthException; +import com.google.gerrit.extensions.restapi.RestApiException; +import com.google.gerrit.reviewdb.client.Branch; +import com.google.gerrit.reviewdb.client.Change; +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.ChangeMessagesUtil; +import com.google.gerrit.server.IdentifiedUser; +import com.google.gerrit.server.extensions.events.ChangeMerged; +import com.google.gerrit.server.extensions.events.GitReferenceUpdated; +import com.google.gerrit.server.git.GitRepositoryManager; +import com.google.gerrit.server.mail.send.MergedSender; +import com.google.gerrit.server.mail.send.RevertedSender; +import com.google.gerrit.server.permissions.PermissionBackend; +import com.google.gerrit.server.permissions.PermissionBackendException; +import com.google.gerrit.server.permissions.ProjectPermission; +import com.google.gerrit.server.permissions.RefPermission; +import com.google.gerrit.server.project.NoSuchProjectException; +import com.google.gerrit.server.project.NoSuchRefException; +import com.google.gerrit.server.query.change.ChangeData; +import com.google.gerrit.server.update.BatchUpdate; +import com.google.gerrit.server.update.UpdateException; +import com.google.gerrit.server.util.time.TimeUtil; +import com.google.gerrit.sshd.SshCommand; +import com.google.gerrit.sshd.CommandMetaData; + +import com.google.gwtorm.server.OrmException; + +import com.google.inject.Inject; +import com.google.inject.Provider; + +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.RefUpdate.Result; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.kohsuke.args4j.Option; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.sql.Timestamp; +import java.util.List; +import java.util.Map.Entry; + + +/** + * A command to report pass or fail status for builds. When a build receives + * pass status, the branch is updated with build ref and all open changes in + * the build are marked as merged. When a build receives fail status, all + * change in the build are marked as new and they need to be staged again. + * + * For example, how to approve a build + * $ ssh -p 29418 localhost gerrit-plugin-qt-workflow staging-approve -p project -b master -i 123 -r=pass + */ +@CommandMetaData(name="staging-approve", description="Report pass or fail status for builds. If passed changed are merged into target branch.") +class QtCommandBuildApprove extends SshCommand { + + @Inject + private PermissionBackend permissionBackend; + + @Inject + private GitRepositoryManager gitManager; + + @Inject + private Provider<ReviewDb> dbProvider; + + @Inject + private MergedSender.Factory mergedSenderFactory; + + @Inject + private RevertedSender.Factory revertedSenderFactory; + + @Inject + private BatchUpdate.Factory updateFactory; + + @Inject + private GitReferenceUpdated referenceUpdated; + + @Inject + private ChangeMerged changeMerged; + + @Inject + private QtUtil qtUtil; + + @Inject + private QtChangeUpdateOp.Factory qtUpdateFactory; + + @Option(name = "--project", aliases = {"-p"}, + required = true, usage = "project name") + private String project; + + @Option(name = "--build-id", aliases = {"-i"}, + required = true, usage = "build branch containing changes, e.g. refs/builds/123 or 123") + private String buildBranch; + + @Option(name = "--result", aliases = {"-r"}, + required = true, usage = "pass or fail") + private String result; + + @Option(name = "--message", aliases = {"-m"}, metaVar ="-|MESSAGE", + usage = "message added to all changes") + private String message; + + @Option(name = "--branch", aliases = {"-b"}, metaVar = "BRANCH", + required = true, usage = "destination branch, e.g. refs/heads/master or just master") + private String destBranch; + + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + + private Repository git; + + /** Parameter value for pass result. */ + private static final String PASS = "pass"; + /** Parameter value for fail result. */ + private static final String FAIL = "fail"; + /** Parameter value for stdin message. */ + private static final String STDIN_MESSAGE = "-"; + + private Project.NameKey projectKey; + private Branch.NameKey buildBranchKey; + private Branch.NameKey destBranchKey; + private Branch.NameKey stagingBranchKey; + private Branch.NameKey destBranchShortKey; + + private List<Entry<ChangeData,RevCommit>> affectedChanges = null; + + @Override + protected void run() throws UnloggedFailure { + logger.atInfo().log("qtcodereview: staging-approve -p %s -i %s -r %s -m %s -b %s", + project, buildBranch, result, message, destBranch); + + readMessageParameter(); + + projectKey = QtUtil.getProjectKey(project); + buildBranchKey = QtUtil.getNameKeyLong(project, QtUtil.R_BUILDS, buildBranch); + destBranchKey = QtUtil.getNameKeyLong(project, QtUtil.R_HEADS, destBranch); + stagingBranchKey = QtUtil.getNameKeyLong(project, QtUtil.R_STAGING, destBranch); + destBranchShortKey = QtUtil.getNameKeyShort(project, QtUtil.R_HEADS, destBranch); + + try { + git = gitManager.openRepository(projectKey); + + // Check required permissions + permissionBackend.user(user).project(projectKey).ref(destBranchKey.get()).check(RefPermission.UPDATE); + permissionBackend.user(user).project(projectKey).ref(stagingBranchKey.get()).check(RefPermission.UPDATE); + permissionBackend.user(user).project(projectKey).ref(buildBranchKey.get()).check(RefPermission.READ); + + if (git.resolve(destBranchKey.get()) == null) throw die("branch not found"); + if (git.resolve(buildBranchKey.get()) == null) throw die("build not found"); + + // Initialize and populate open changes list. + affectedChanges = qtUtil.listChangesNotMerged(git, buildBranchKey, destBranchKey); + + // Notify user that build did not have any open changes. The build has already been approved. + if (affectedChanges.isEmpty()) { + logger.atInfo().log("qtcodereview: staging-approve build %s already in project %s branch %s", + buildBranch, projectKey, destBranchKey); + throw die("No open changes in the build branch"); + } + + if (result.toLowerCase().equals(PASS)) { + approveBuildChanges(); + } else if (result.toLowerCase().equals(FAIL)) { + rejectBuildChanges(); + } else { + throw die("result argument accepts only value pass or fail."); + } + + } catch (AuthException e) { + throw die("not authorized"); + } catch (PermissionBackendException e) { + throw die("git permission error"); + } catch (RepositoryNotFoundException e) { + throw die("project not found"); + } catch (IOException e) { + throw die(e.getMessage()); + } catch (OrmException e) { + throw die("Failed to access database"); + } catch (QtUtil.BranchNotFoundException e) { + throw die("invalid branch " + e.getMessage()); + } catch (NoSuchRefException e) { + throw die("invalid reference " + e.getMessage()); + } catch (UpdateException | RestApiException e) { + logger.atSevere().log("qtcodereview: staging-napprove failed to update change status %s", e); + throw die("Failed to update change status"); + } catch (QtUtil.MergeConflictException e) { + String msg = String.format("Merge build %s to branch %s failed", buildBranch, destBranchKey); + logger.atSevere().log("qtcodereview: %s", msg); + throw die(String.format("Merge conflict! build branch %s into %s failed", buildBranch, destBranch)); + } finally { + if (git != null) git.close(); + } + } + + private void approveBuildChanges() throws QtUtil.MergeConflictException, NoSuchRefException, + IOException, UpdateException, RestApiException, + OrmException { + if (message == null) message = String.format("Change merged into branch %s", destBranchKey); + + ObjectId oldId = git.resolve(destBranchKey.get()); + + QtUtil.mergeBranches(user.asIdentifiedUser(), git, buildBranchKey, destBranchKey); + + updateChanges(affectedChanges, Change.Status.MERGED, null, + message, ChangeMessagesUtil.TAG_MERGED, true); + + logger.atInfo().log("qtcodereview: staging-approve build %s merged into branch %s", + buildBranch, destBranchKey); + + ObjectId newId = git.resolve(destBranchKey.get()); + // send ref updated event only if there are changes to build + if (!newId.equals(oldId)) { + referenceUpdated.fire(projectKey, destBranchKey.get(), oldId, newId, user.asIdentifiedUser().state()); + } + } + + private void rejectBuildChanges() throws QtUtil.MergeConflictException, UpdateException, + RestApiException, OrmException { + if (message == null) message = String.format("Change rejected for branch %s", destBranchKey); + + updateChanges(affectedChanges, Change.Status.NEW, Change.Status.INTEGRATING, + message, ChangeMessagesUtil.TAG_REVERT, false); + + // need to rebuild the staging ref because the reject changes need to be removed from there + qtUtil.rebuildStagingBranch(git, user.asIdentifiedUser(), projectKey, stagingBranchKey, destBranchShortKey); + + logger.atInfo().log("qtcodereview: staging-approve build %s rejected for branch %s", + buildBranch, destBranchKey); + } + + private void updateChanges(List<Entry<ChangeData,RevCommit>> list, + Change.Status status, + Change.Status oldStatus, + String changeMessage, + String tag, + Boolean passed) + throws UpdateException, RestApiException, OrmException { + // do the db update + QtChangeUpdateOp op = qtUpdateFactory.create(status, changeMessage, null, tag, null); + try (BatchUpdate u = updateFactory.create(dbProvider.get(), projectKey, user, TimeUtil.nowTs())) { + for (Entry<ChangeData,RevCommit> item : list) { + Change change = item.getKey().change(); + if (oldStatus == null || change.getStatus() == oldStatus) { + u.addOp(change.getId(), op); + } + } + u.execute(); + } + + // do rest + for (Entry<ChangeData,RevCommit> item : list) { + ChangeData cd = item.getKey(); + Change change = cd.change(); + if (passed) { + sendMergeEvent(cd); + logger.atInfo().log("qtcodereview: staging-approve change %s merged into %s", + change, destBranchKey); + } else { + logger.atInfo().log("qtcodereview: staging-approve change %s rejected for %s", + change, destBranchKey); + } + + sendResultEmail(change.getId(), passed); + } + } + + private void sendMergeEvent(ChangeData changeData) throws OrmException { + Timestamp ts = TimeUtil.nowTs(); + + try { + PatchSet ps = changeData.currentPatchSet(); + changeMerged.fire(changeData.change(), ps, user.asIdentifiedUser().state(), + ps.getRevision().get(), ts); + } catch ( OrmException e) { + logger.atInfo().log("qtcodereview: staging-approve sending merge event failed for %s", + changeData.change()); + } + } + + private void readMessageParameter() throws UnloggedFailure { + if (message == null) return; + + try { + // User will submit message through stdin. + if (message.equals(STDIN_MESSAGE)) { + // Clear stdin indicator. + message = ""; + + // Read message from stdin. + BufferedReader stdin = new BufferedReader(new InputStreamReader(in, "UTF-8")); + String line; + while ((line = stdin.readLine()) != null) { + message += line + "\n"; + } + } + } catch (IOException e) { + throw new UnloggedFailure(1, "fatal: " + e.getMessage(), e); + } + } + + private void sendResultEmail(Change.Id changeId, Boolean passed) { + try { + if (passed) { + MergedSender mcm = mergedSenderFactory.create(projectKey, changeId); + mcm.send(); + } else { + RevertedSender rcm = revertedSenderFactory.create(projectKey, changeId); + rcm.send(); + } + } catch (Exception e) { + logger.atWarning().log("qtcodereview: staging-approve Cannot email notification for %s %s", changeId, e); + } + } + +} diff --git a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtSshModule.java b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtSshModule.java index 11300c3..306a5cb 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtSshModule.java +++ b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtSshModule.java @@ -11,6 +11,7 @@ class QtSshModule extends PluginCommandModule { @Override protected void configureCommands() { command(QtCommandPing.class); + command(QtCommandBuildApprove.class); command(QtCommandNewBuild.class); command(QtCommandListStaging.class); command(QtCommandRebuildStaging.class); diff --git a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtUtil.java b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtUtil.java index 8033d80..b2da710 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtUtil.java +++ b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtUtil.java @@ -51,11 +51,13 @@ import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.transport.ReceiveCommand; import java.io.IOException; +import java.sql.Timestamp; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.TimeZone; /** @@ -475,4 +477,54 @@ public class QtUtil { return revWalk.parseCommit(objInserter.insert(mergeCommit)); } + public static RefUpdate.Result mergeBranches(IdentifiedUser user, + Repository git, + final Branch.NameKey branch, + final Branch.NameKey destination) + throws NoSuchRefException, IOException, MergeConflictException { + + ObjectId srcId = git.resolve(branch.get()); + if (srcId == null) throw new NoSuchRefException("Invalid Revision: " + branch); + + return mergeObjectToBranch(user, git, srcId, destination); + } + + private static RefUpdate.Result mergeObjectToBranch(IdentifiedUser user, + Repository git, + ObjectId srcId, + final Branch.NameKey destination) + throws NoSuchRefException, IOException, MergeConflictException { + + Ref destRef = git.getRefDatabase().getRef(destination.get()); + if (destRef == null) throw new NoSuchRefException("No such branch: " + destination); + + ObjectId destId = git.resolve(destination.get()); + if (destId == null) throw new NoSuchRefException("Invalid Revision: " + destination); + + RevWalk revWalk = new RevWalk(git); + try { + + ObjectInserter objInserter = git.newObjectInserter(); + RevCommit mergeTip = revWalk.lookupCommit(destId); + RevCommit toMerge = revWalk.lookupCommit(srcId); + PersonIdent committer = user.newCommitterIdent(new Timestamp(System.currentTimeMillis()), TimeZone.getDefault()); + + RevCommit mergeCommit = merge(committer, + git, + objInserter, + revWalk, + toMerge, + mergeTip, + false); + objInserter.flush(); + logger.atInfo().log("qtcodereview: merge commit for %s added to %s", srcId, destination); + + RefUpdate refUpdate = git.updateRef(destination.get()); + refUpdate.setNewObjectId(mergeCommit); + return refUpdate.update(); + } finally { + revWalk.dispose(); + } + } + } diff --git a/src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtCodeReviewIT.java b/src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtCodeReviewIT.java index 5fbb78d..2f6d28b 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtCodeReviewIT.java +++ b/src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtCodeReviewIT.java @@ -128,6 +128,30 @@ public class QtCodeReviewIT extends LightweightPluginDaemonTest { resetEvents(); } + protected void QtApproveBuild(String branch, String buildId) throws Exception { + String commandStr; + commandStr ="gerrit-plugin-qt-workflow staging-approve"; + commandStr += " --project " + project.get(); + commandStr += " --branch " + branch; + commandStr += " --build-id "+ buildId; + commandStr += " --result pass"; + commandStr += " --message thebuildpassed"; + String resultStr = adminSshSession.exec(commandStr); + assertThat(adminSshSession.getError()).isNull(); + } + + protected void QtFailBuild(String branch, String buildId) throws Exception { + String commandStr; + commandStr ="gerrit-plugin-qt-workflow staging-approve"; + commandStr += " --project " + project.get(); + commandStr += " --branch " + branch; + commandStr += " --build-id "+ buildId; + commandStr += " --result fail"; + commandStr += " --message thebuildfailed"; + String resultStr = adminSshSession.exec(commandStr); + assertThat(adminSshSession.getError()).isNull(); + } + protected PushOneCommit.Result pushCommit(String branch, String message, String file, diff --git a/src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandBuildApproveIT.java b/src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandBuildApproveIT.java new file mode 100644 index 0000000..cb3e10a --- /dev/null +++ b/src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandBuildApproveIT.java @@ -0,0 +1,345 @@ +// Copyright (C) 2019 The Qt Company + +package com.googlesource.gerrit.plugins.qtcodereview; + +import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.gerrit.acceptance.PushOneCommit; +import com.google.gerrit.acceptance.TestPlugin; +import com.google.gerrit.acceptance.UseSsh; + +import com.google.gerrit.common.data.Permission; +import com.google.gerrit.reviewdb.client.Change; +import com.google.gerrit.reviewdb.client.ChangeMessage; + +import org.eclipse.jgit.revwalk.RevCommit; + +import java.util.ArrayList; +import java.io.StringBufferInputStream; + +import org.junit.Before; +import org.junit.Test; + +@TestPlugin( + name = "gerrit-plugin-qt-workflow", + sysModule = "com.googlesource.gerrit.plugins.qtcodereview.QtModule", + sshModule = "com.googlesource.gerrit.plugins.qtcodereview.QtSshModule" +) + +@UseSsh +public class QtCommandBuildApproveIT extends QtCodeReviewIT { + + private final String MERGED_MSG = "thebuildwasapproved"; + private final String FAILED_MSG = "thebuildfailed"; + + @Before + public void SetDefaultPermissions() throws Exception { + grant(project, "refs/heads/master", Permission.QT_STAGE, false, REGISTERED_USERS); + grant(project, "refs/staging/*", Permission.PUSH, false, adminGroupUuid()); + grant(project, "refs/builds/*", Permission.CREATE, false, adminGroupUuid()); + } + + @Test + public void singleChange_New_Staged_Integrating_Merged() throws Exception { + PushOneCommit.Result c = pushCommit("master", "commitmsg1", "file1", "content1"); + approve(c.getChangeId()); + QtStage(c); + QtNewBuild("master", "test-build-100"); + + RevCommit updatedHead = qtApproveBuild("master", "test-build-100", c, null); + } + + @Test + public void multiChange_New_Staged_Integrating_Merged() throws Exception { + // Push 3 independent commits + RevCommit initialHead = getRemoteHead(); + PushOneCommit.Result c1 = pushCommit("master", "commitmsg1", "file1", "content1"); + testRepo.reset(initialHead); + PushOneCommit.Result c2 = pushCommit("master", "commitmsg2", "file2", "content2"); + testRepo.reset(initialHead); + PushOneCommit.Result c3 = pushCommit("master", "commitmsg3", "file3", "content3"); + + approve(c1.getChangeId()); + approve(c2.getChangeId()); + approve(c3.getChangeId()); + QtStage(c1); + QtStage(c2); + QtStage(c3); + + QtNewBuild("master", "test-build-101"); + + RevCommit updatedHead = qtApproveBuild("master", "test-build-101", c3, null); + Change change = c1.getChange().change(); + assertThat(change.getStatus()).isEqualTo(Change.Status.MERGED); + change = c2.getChange().change(); + assertThat(change.getStatus()).isEqualTo(Change.Status.MERGED); + } + + @Test + public void singleChange_New_Staged_Integrating_Fail() throws Exception { + RevCommit initialHead = getRemoteHead(); + PushOneCommit.Result c = pushCommit("master", "commitmsg1", "file1", "content1"); + approve(c.getChangeId()); + QtStage(c); + QtNewBuild("master", "test-build-200"); + + RevCommit updatedHead = qtFailBuild("master", "test-build-200", c, initialHead); + } + + @Test + public void multiChange_New_Staged_Integrating_Failed() throws Exception { + // Push 3 independent commits + RevCommit initialHead = getRemoteHead(); + PushOneCommit.Result c1 = pushCommit("master", "commitmsg1", "file1", "content1"); + testRepo.reset(initialHead); + PushOneCommit.Result c2 = pushCommit("master", "commitmsg2", "file2", "content2"); + testRepo.reset(initialHead); + PushOneCommit.Result c3 = pushCommit("master", "commitmsg3", "file3", "content3"); + + approve(c1.getChangeId()); + approve(c2.getChangeId()); + approve(c3.getChangeId()); + QtStage(c1); + QtStage(c2); + QtStage(c3); + + QtNewBuild("master", "test-build-201"); + + RevCommit updatedHead = qtFailBuild("master", "test-build-201", c3, initialHead); + Change change = c1.getChange().change(); + assertThat(change.getStatus()).isEqualTo(Change.Status.NEW); + change = c2.getChange().change(); + assertThat(change.getStatus()).isEqualTo(Change.Status.NEW); + } + + @Test + public void errorApproveBuild_NoPermission() throws Exception { + PushOneCommit.Result c = pushCommit("master", "commitmsg1", "file1", "content1"); + approve(c.getChangeId()); + QtStage(c); + QtNewBuild("master", "test-build-600"); + + String commandStr; + commandStr ="gerrit-plugin-qt-workflow staging-approve"; + commandStr += " --project " + project.get(); + commandStr += " --branch master"; + commandStr += " --build-id test-build-600"; + commandStr += " --result pass"; + commandStr += " --message " + MERGED_MSG; + String resultStr = userSshSession.exec(commandStr); + assertThat(userSshSession.getError()).contains("not authorized"); + } + + @Test + public void errorApproveBuild_RepoNotFound() throws Exception { + PushOneCommit.Result c = pushCommit("master", "commitmsg1", "file1", "content1"); + approve(c.getChangeId()); + QtStage(c); + QtNewBuild("master", "test-build-600"); + + String commandStr; + commandStr ="gerrit-plugin-qt-workflow staging-approve"; + commandStr += " --project notarepo" ; + commandStr += " --branch master"; + commandStr += " --build-id test-build-600"; + commandStr += " --result pass"; + commandStr += " --message " + MERGED_MSG; + String resultStr = adminSshSession.exec(commandStr); + assertThat(adminSshSession.getError()).contains("project not found"); + } + + @Test + public void errorApproveBuild_WrongParameter() throws Exception { + PushOneCommit.Result c = pushCommit("master", "commitmsg1", "file1", "content1"); + approve(c.getChangeId()); + QtStage(c); + QtNewBuild("master", "test-build-601"); + + String resultStr = qtApproveBuildExpectFail("maybe", "master", "test-build-601"); + assertThat(resultStr).contains("result argument accepts only value pass or fail"); + } + + @Test + public void errorApproveBuild_NoChanges() throws Exception { + PushOneCommit.Result c = pushCommit("master", "commitmsg1", "file1", "content1"); + approve(c.getChangeId()); + QtStage(c); + QtNewBuild("master", "test-build-602"); + RevCommit updatedHead = qtApproveBuild("master", "test-build-602", c, null); + + String resultStr = qtApproveBuildExpectFail("pass", "master", "test-build-602"); + assertThat(resultStr).contains("No open changes in the build branch"); + } + + @Test + public void errorApproveBuild_NonExistingBranch() throws Exception { + PushOneCommit.Result c = pushCommit("master", "commitmsg1", "file1", "content1"); + approve(c.getChangeId()); + QtStage(c); + QtNewBuild("master", "test-build-603"); + + String resultStr = qtApproveBuildExpectFail("pass", "invalidbranch", "test-build-603"); + assertThat(resultStr).contains("branch not found"); + } + + @Test + public void errorApproveBuild_NonExistingBuild() throws Exception { + PushOneCommit.Result c = pushCommit("master", "commitmsg1", "file1", "content1"); + approve(c.getChangeId()); + QtStage(c); + QtNewBuild("master", "test-build-604"); + + String resultStr = qtApproveBuildExpectFail("pass", "master", "doesnotexist"); + assertThat(resultStr).contains("build not found"); + } + + @Test + public void approveBuild_MultiLineMessage() throws Exception { + PushOneCommit.Result c = pushCommit("master", "commitmsg1", "file1", "content1"); + approve(c.getChangeId()); + QtStage(c); + QtNewBuild("master", "test-build-605"); + + String commandStr; + commandStr ="gerrit-plugin-qt-workflow staging-approve"; + commandStr += " --project " + project.get(); + commandStr += " --branch master"; + commandStr += " --build-id test-build-605"; + commandStr += " --result pass"; + commandStr += " --message -"; + String multiMessage = "the build\nwas\n\"approved\"\n"; + StringBufferInputStream input = new StringBufferInputStream(multiMessage); + + String resultStr = adminSshSession.exec(commandStr, input); + assertThat(resultStr).isEqualTo(""); + assertThat(adminSshSession.getError()).isNull(); + + ArrayList<ChangeMessage> messages = new ArrayList(c.getChange().messages()); + assertThat(messages.get(messages.size()-1).getMessage()).isEqualTo(multiMessage); // check last message + } + + private RevCommit qtApproveBuild(String branch, + String buildId, + PushOneCommit.Result expectedContent, + RevCommit expectedHead) + throws Exception { + String stagingRef = R_STAGING + branch; + String branchRef = R_HEADS + branch; + String buildRef = R_BUILDS + buildId; + + RevCommit stagingHeadOld = getRemoteHead(project, stagingRef); + RevCommit initialHead = getRemoteHead(project, branchRef); + String commandStr; + + commandStr ="gerrit-plugin-qt-workflow staging-approve"; + commandStr += " --project " + project.get(); + commandStr += " --branch " + branch; + commandStr += " --build-id " + buildId; + commandStr += " --result pass"; + commandStr += " --message " + MERGED_MSG; + String resultStr = adminSshSession.exec(commandStr); + assertThat(adminSshSession.getError()).isNull(); + + RevCommit buildHead = getRemoteHead(project, buildRef); + assertThat(buildHead.getId()).isNotNull(); // build ref is still there + + RevCommit updatedHead = getRemoteHead(project, branchRef); + if (expectedContent != null && expectedHead == null) { + RevCommit commit = expectedContent.getCommit(); + assertCherryPick(updatedHead, commit, getCurrentPatchSHA(expectedContent)); + expectedHead = updatedHead; + } else { + assertThat(updatedHead).isEqualTo(expectedHead); // master is updated + } + + RevCommit stagingHead = getRemoteHead(project, stagingRef); + assertThat(stagingHead).isEqualTo(stagingHeadOld); // staging remains the same + + assertRefUpdatedEvents(branchRef, initialHead, expectedHead); + resetEvents(); + + Change change = expectedContent.getChange().change(); + assertThat(change.getStatus()).isEqualTo(Change.Status.MERGED); + + ArrayList<ChangeMessage> messages = new ArrayList(expectedContent.getChange().messages()); + assertThat(messages.get(messages.size()-1).getMessage()).isEqualTo(MERGED_MSG); // check last message + + return updatedHead; + } + + private RevCommit qtFailBuild(String branch, + String buildId, + PushOneCommit.Result c, + RevCommit expectedStagingHead) + throws Exception { + String stagingRef = R_STAGING + branch; + String branchRef = R_HEADS + branch; + String buildRef = R_BUILDS + buildId; + RevCommit stagingHeadOld = getRemoteHead(project, stagingRef); + RevCommit initialHead = getRemoteHead(project, branchRef); + String commandStr; + + commandStr ="gerrit-plugin-qt-workflow staging-approve"; + commandStr += " --project " + project.get(); + commandStr += " --branch " + branch; + commandStr += " --build-id " + buildId; + commandStr += " --result fail"; + commandStr += " --message " + FAILED_MSG; + String resultStr = adminSshSession.exec(commandStr); + assertThat(adminSshSession.getError()).isNull(); + + RevCommit updatedHead = getRemoteHead(project, branchRef); + assertThat(updatedHead.getId()).isEqualTo(initialHead.getId()); // master is not updated + + RevCommit stagingHead = getRemoteHead(project, stagingRef); + + if (c != null && expectedStagingHead == null) { + assertCherryPick(stagingHead, c.getCommit(), getCurrentPatchSHA(c)); + expectedStagingHead = stagingHead; + } + assertThat(stagingHead).isEqualTo(expectedStagingHead); // staging is rebuild + + RevCommit buildHead = getRemoteHead(project, buildRef); + assertThat(buildHead.getId()).isNotNull(); // build ref is still there + + assertRefUpdatedEvents(stagingRef, stagingHeadOld, stagingHead); // staging is rebuild + + Change change = c.getChange().change(); + assertThat(change.getStatus()).isEqualTo(Change.Status.NEW); + + ArrayList<ChangeMessage> messages = new ArrayList(c.getChange().messages()); + assertThat(messages.get(messages.size()-1).getMessage()).isEqualTo(FAILED_MSG); // check last message + + return updatedHead; + } + + private String qtApproveBuildExpectFail(String cmd, + String branch, + String buildId) + throws Exception { + String stagingRef = R_STAGING + branch; + String branchRef = R_HEADS + branch; + RevCommit initialHead = getRemoteHead(project, branchRef); + RevCommit stagingHeadOld = getRemoteHead(project, stagingRef); + String commandStr; + + commandStr ="gerrit-plugin-qt-workflow staging-approve"; + commandStr += " --project " + project.get(); + commandStr += " --branch " + branch; + commandStr += " --build-id " + buildId; + commandStr += " --result " + cmd; + commandStr += " --message " + MERGED_MSG; + String resultStr = adminSshSession.exec(commandStr); + + RevCommit updatedHead = getRemoteHead(project, branchRef); + if (updatedHead != null) assertThat(updatedHead).isEqualTo(initialHead); // master is not updated + + RevCommit stagingHead = getRemoteHead(project, stagingRef); + if (stagingHead != null) assertThat(stagingHead).isEqualTo(stagingHeadOld); // staging is not updated + + return adminSshSession.getError(); + } + +} |