/*
Copyright 2015 Google Inc. All rights reserved.
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 review
import (
"github.com/google/git-appraise/repository"
"github.com/google/git-appraise/review/comment"
"github.com/google/git-appraise/review/request"
"sort"
"testing"
)
func TestCommentSorting(t *testing.T) {
sampleComments := []*comment.Comment{
&comment.Comment{
Timestamp: "012400",
Description: "Fourth",
},
&comment.Comment{
Timestamp: "012400",
Description: "Fifth",
},
&comment.Comment{
Timestamp: "012346",
Description: "Second",
},
&comment.Comment{
Timestamp: "012345",
Description: "First",
},
&comment.Comment{
Timestamp: "012347",
Description: "Third",
},
}
sort.Stable(commentsByTimestamp(sampleComments))
descriptions := []string{}
for _, comment := range sampleComments {
descriptions = append(descriptions, comment.Description)
}
if !(descriptions[0] == "First" && descriptions[1] == "Second" && descriptions[2] == "Third" && descriptions[3] == "Fourth" && descriptions[4] == "Fifth") {
t.Fatalf("Comment ordering failed. Got %v", sampleComments)
}
}
func TestThreadSorting(t *testing.T) {
sampleThreads := []CommentThread{
CommentThread{
Comment: comment.Comment{
Timestamp: "012400",
Description: "Fourth",
},
},
CommentThread{
Comment: comment.Comment{
Timestamp: "012400",
Description: "Fifth",
},
},
CommentThread{
Comment: comment.Comment{
Timestamp: "012346",
Description: "Second",
},
},
CommentThread{
Comment: comment.Comment{
Timestamp: "012345",
Description: "First",
},
},
CommentThread{
Comment: comment.Comment{
Timestamp: "012347",
Description: "Third",
},
},
}
sort.Stable(byTimestamp(sampleThreads))
descriptions := []string{}
for _, thread := range sampleThreads {
descriptions = append(descriptions, thread.Comment.Description)
}
if !(descriptions[0] == "First" && descriptions[1] == "Second" && descriptions[2] == "Third" && descriptions[3] == "Fourth" && descriptions[4] == "Fifth") {
t.Fatalf("Comment thread ordering failed. Got %v", sampleThreads)
}
}
func TestRequestSorting(t *testing.T) {
sampleRequests := []request.Request{
request.Request{
Timestamp: "012400",
Description: "Fourth",
},
request.Request{
Timestamp: "012400",
Description: "Fifth",
},
request.Request{
Timestamp: "012346",
Description: "Second",
},
request.Request{
Timestamp: "012345",
Description: "First",
},
request.Request{
Timestamp: "012347",
Description: "Third",
},
}
sort.Stable(requestsByTimestamp(sampleRequests))
descriptions := []string{}
for _, r := range sampleRequests {
descriptions = append(descriptions, r.Description)
}
if !(descriptions[0] == "First" && descriptions[1] == "Second" && descriptions[2] == "Third" && descriptions[3] == "Fourth" && descriptions[4] == "Fifth") {
t.Fatalf("Review request ordering failed. Got %v", sampleRequests)
}
}
func validateUnresolved(t *testing.T, resolved *bool) {
if resolved != nil {
t.Fatalf("Expected resolved status to be unset, but instead it was %v", *resolved)
}
}
func validateAccepted(t *testing.T, resolved *bool) {
if resolved == nil {
t.Fatal("Expected resolved status to be true, but it was unset")
}
if !*resolved {
t.Fatal("Expected resolved status to be true, but it was false")
}
}
func validateRejected(t *testing.T, resolved *bool) {
if resolved == nil {
t.Fatal("Expected resolved status to be false, but it was unset")
}
if *resolved {
t.Fatal("Expected resolved status to be false, but it was true")
}
}
func (commentThread *CommentThread) validateUnresolved(t *testing.T) {
validateUnresolved(t, commentThread.Resolved)
}
func (commentThread *CommentThread) validateAccepted(t *testing.T) {
validateAccepted(t, commentThread.Resolved)
}
func (commentThread *CommentThread) validateRejected(t *testing.T) {
validateRejected(t, commentThread.Resolved)
}
func TestSimpleAcceptedThreadStatus(t *testing.T) {
resolved := true
simpleThread := CommentThread{
Comment: comment.Comment{
Resolved: &resolved,
},
}
simpleThread.updateResolvedStatus()
simpleThread.validateAccepted(t)
}
func TestSimpleRejectedThreadStatus(t *testing.T) {
resolved := false
simpleThread := CommentThread{
Comment: comment.Comment{
Resolved: &resolved,
},
}
simpleThread.updateResolvedStatus()
simpleThread.validateRejected(t)
}
func TestFYIThenAcceptedThreadStatus(t *testing.T) {
accepted := true
sampleThread := CommentThread{
Comment: comment.Comment{
Resolved: nil,
},
Children: []CommentThread{
CommentThread{
Comment: comment.Comment{
Timestamp: "012345",
Resolved: &accepted,
},
},
},
}
sampleThread.updateResolvedStatus()
sampleThread.validateUnresolved(t)
}
func TestFYIThenFYIThreadStatus(t *testing.T) {
sampleThread := CommentThread{
Comment: comment.Comment{
Resolved: nil,
},
Children: []CommentThread{
CommentThread{
Comment: comment.Comment{
Timestamp: "012345",
Resolved: nil,
},
},
},
}
sampleThread.updateResolvedStatus()
sampleThread.validateUnresolved(t)
}
func TestFYIThenRejectedThreadStatus(t *testing.T) {
rejected := false
sampleThread := CommentThread{
Comment: comment.Comment{
Resolved: nil,
},
Children: []CommentThread{
CommentThread{
Comment: comment.Comment{
Timestamp: "012345",
Resolved: &rejected,
},
},
},
}
sampleThread.updateResolvedStatus()
sampleThread.validateRejected(t)
}
func TestAcceptedThenAcceptedThreadStatus(t *testing.T) {
accepted := true
sampleThread := CommentThread{
Comment: comment.Comment{
Resolved: &accepted,
},
Children: []CommentThread{
CommentThread{
Comment: comment.Comment{
Timestamp: "012345",
Resolved: &accepted,
},
},
},
}
sampleThread.updateResolvedStatus()
sampleThread.validateAccepted(t)
}
func TestAcceptedThenFYIThreadStatus(t *testing.T) {
accepted := true
sampleThread := CommentThread{
Comment: comment.Comment{
Resolved: &accepted,
},
Children: []CommentThread{
CommentThread{
Comment: comment.Comment{
Timestamp: "012345",
Resolved: nil,
},
},
},
}
sampleThread.updateResolvedStatus()
sampleThread.validateAccepted(t)
}
func TestAcceptedThenRejectedThreadStatus(t *testing.T) {
accepted := true
rejected := false
sampleThread := CommentThread{
Comment: comment.Comment{
Resolved: &accepted,
},
Children: []CommentThread{
CommentThread{
Comment: comment.Comment{
Timestamp: "012345",
Resolved: &rejected,
},
},
},
}
sampleThread.updateResolvedStatus()
sampleThread.validateRejected(t)
}
func TestRejectedThenAcceptedThreadStatus(t *testing.T) {
accepted := true
rejected := false
sampleThread := CommentThread{
Comment: comment.Comment{
Resolved: &rejected,
},
Children: []CommentThread{
CommentThread{
Comment: comment.Comment{
Timestamp: "012345",
Resolved: &accepted,
},
},
},
}
sampleThread.updateResolvedStatus()
sampleThread.validateUnresolved(t)
}
func TestRejectedThenFYIThreadStatus(t *testing.T) {
rejected := false
sampleThread := CommentThread{
Comment: comment.Comment{
Resolved: &rejected,
},
Children: []CommentThread{
CommentThread{
Comment: comment.Comment{
Timestamp: "012345",
Resolved: nil,
},
},
},
}
sampleThread.updateResolvedStatus()
sampleThread.validateRejected(t)
}
func TestRejectedThenRejectedThreadStatus(t *testing.T) {
rejected := false
sampleThread := CommentThread{
Comment: comment.Comment{
Resolved: &rejected,
},
Children: []CommentThread{
CommentThread{
Comment: comment.Comment{
Timestamp: "012345",
Resolved: &rejected,
},
},
},
}
sampleThread.updateResolvedStatus()
sampleThread.validateRejected(t)
}
func TestRejectedThenAcceptedThreadsStatus(t *testing.T) {
accepted := true
rejected := false
threads := []CommentThread{
CommentThread{
Comment: comment.Comment{
Timestamp: "012345",
Resolved: &rejected,
},
},
CommentThread{
Comment: comment.Comment{
Timestamp: "012346",
Resolved: &accepted,
},
},
}
status := updateThreadsStatus(threads)
validateRejected(t, status)
}
func TestRejectedThenFYIThreadsStatus(t *testing.T) {
rejected := false
threads := []CommentThread{
CommentThread{
Comment: comment.Comment{
Timestamp: "012345",
Resolved: &rejected,
},
},
CommentThread{
Comment: comment.Comment{
Timestamp: "012346",
Resolved: nil,
},
},
}
status := updateThreadsStatus(threads)
validateRejected(t, status)
}
func TestRejectedThenRejectedThreadsStatus(t *testing.T) {
rejected := false
threads := []CommentThread{
CommentThread{
Comment: comment.Comment{
Timestamp: "012345",
Resolved: &rejected,
},
},
CommentThread{
Comment: comment.Comment{
Timestamp: "012346",
Resolved: &rejected,
},
},
}
status := updateThreadsStatus(threads)
validateRejected(t, status)
}
func TestAcceptedThenAcceptedThreadsStatus(t *testing.T) {
accepted := true
threads := []CommentThread{
CommentThread{
Comment: comment.Comment{
Timestamp: "012345",
Resolved: &accepted,
},
},
CommentThread{
Comment: comment.Comment{
Timestamp: "012346",
Resolved: &accepted,
},
},
}
status := updateThreadsStatus(threads)
validateAccepted(t, status)
}
func TestAcceptedThenFYIThreadsStatus(t *testing.T) {
accepted := true
threads := []CommentThread{
CommentThread{
Comment: comment.Comment{
Timestamp: "012345",
Resolved: &accepted,
},
},
CommentThread{
Comment: comment.Comment{
Timestamp: "012346",
Resolved: nil,
},
},
}
status := updateThreadsStatus(threads)
validateAccepted(t, status)
}
func TestAcceptedThenRejectedThreadsStatus(t *testing.T) {
accepted := true
rejected := false
threads := []CommentThread{
CommentThread{
Comment: comment.Comment{
Timestamp: "012345",
Resolved: &accepted,
},
},
CommentThread{
Comment: comment.Comment{
Timestamp: "012346",
Resolved: &rejected,
},
},
}
status := updateThreadsStatus(threads)
validateRejected(t, status)
}
func TestFYIThenAcceptedThreadsStatus(t *testing.T) {
accepted := true
threads := []CommentThread{
CommentThread{
Comment: comment.Comment{
Timestamp: "012345",
Resolved: nil,
},
},
CommentThread{
Comment: comment.Comment{
Timestamp: "012346",
Resolved: &accepted,
},
},
}
status := updateThreadsStatus(threads)
validateAccepted(t, status)
}
func TestFYIThenFYIThreadsStatus(t *testing.T) {
threads := []CommentThread{
CommentThread{
Comment: comment.Comment{
Timestamp: "012345",
Resolved: nil,
},
},
CommentThread{
Comment: comment.Comment{
Timestamp: "012346",
Resolved: nil,
},
},
}
status := updateThreadsStatus(threads)
validateUnresolved(t, status)
}
func TestFYIThenRejectedThreadsStatus(t *testing.T) {
rejected := false
threads := []CommentThread{
CommentThread{
Comment: comment.Comment{
Timestamp: "012345",
Resolved: nil,
},
},
CommentThread{
Comment: comment.Comment{
Timestamp: "012346",
Resolved: &rejected,
},
},
}
status := updateThreadsStatus(threads)
validateRejected(t, status)
}
func TestBuildCommentThreads(t *testing.T) {
rejected := false
accepted := true
root := comment.Comment{
Timestamp: "012345",
Resolved: nil,
Description: "root",
}
rootHash, err := root.Hash()
if err != nil {
t.Fatal(err)
}
child := comment.Comment{
Timestamp: "012346",
Resolved: nil,
Parent: rootHash,
Description: "child",
}
childHash, err := child.Hash()
updatedChild := comment.Comment{
Timestamp: "012346",
Resolved: &rejected,
Original: childHash,
Description: "updated child",
}
updatedChildHash, err := updatedChild.Hash()
if err != nil {
t.Fatal(err)
}
leaf := comment.Comment{
Timestamp: "012347",
Resolved: &accepted,
Parent: childHash,
Description: "leaf",
}
leafHash, err := leaf.Hash()
if err != nil {
t.Fatal(err)
}
commentsByHash := map[string]comment.Comment{
rootHash: root,
childHash: child,
updatedChildHash: updatedChild,
leafHash: leaf,
}
threads := buildCommentThreads(commentsByHash)
if len(threads) != 1 {
t.Fatalf("Unexpected threads: %v", threads)
}
rootThread := threads[0]
if rootThread.Comment.Description != "root" {
t.Fatalf("Unexpected root thread: %v", rootThread)
}
if !rootThread.Edited {
t.Fatalf("Unexpected root thread edited status: %v", rootThread)
}
if len(rootThread.Children) != 1 {
t.Fatalf("Unexpected root children: %v", rootThread.Children)
}
rootChild := rootThread.Children[0]
if rootChild.Comment.Description != "updated child" {
t.Fatalf("Unexpected updated child: %v", rootChild)
}
if rootChild.Original.Description != "child" {
t.Fatalf("Unexpected original child: %v", rootChild)
}
if len(rootChild.Edits) != 1 {
t.Fatalf("Unexpected child history: %v", rootChild.Edits)
}
if len(rootChild.Children) != 1 {
t.Fatalf("Unexpected leaves: %v", rootChild.Children)
}
threadLeaf := rootChild.Children[0]
if threadLeaf.Comment.Description != "leaf" {
t.Fatalf("Unexpected leaf: %v", threadLeaf)
}
if len(threadLeaf.Children) != 0 {
t.Fatalf("Unexpected leaf children: %v", threadLeaf.Children)
}
if threadLeaf.Edited {
t.Fatalf("Unexpected leaf edited status: %v", threadLeaf)
}
}
func TestGetHeadCommit(t *testing.T) {
repo := repository.NewMockRepoForTest()
submittedSimpleReview, err := Get(repo, repository.TestCommitB)
if err != nil {
t.Fatal(err)
}
submittedSimpleReviewHead, err := submittedSimpleReview.GetHeadCommit()
if err != nil {
t.Fatal("Unable to compute the head commit for a known review of a simple commit: ", err)
}
if submittedSimpleReviewHead != repository.TestCommitB {
t.Fatal("Unexpected head commit computed for a known review of a simple commit.")
}
submittedModifiedReview, err := Get(repo, repository.TestCommitD)
if err != nil {
t.Fatal(err)
}
submittedModifiedReviewHead, err := submittedModifiedReview.GetHeadCommit()
if err != nil {
t.Fatal("Unable to compute the head commit for a known, multi-commit review: ", err)
}
if submittedModifiedReviewHead != repository.TestCommitE {
t.Fatal("Unexpected head commit for a known, multi-commit review.")
}
pendingReview, err := Get(repo, repository.TestCommitG)
if err != nil {
t.Fatal(err)
}
pendingReviewHead, err := pendingReview.GetHeadCommit()
if err != nil {
t.Fatal("Unable to compute the head commit for a known review of a merge commit: ", err)
}
if pendingReviewHead != repository.TestCommitI {
t.Fatal("Unexpected head commit computed for a pending review.")
}
}
func TestGetBaseCommit(t *testing.T) {
repo := repository.NewMockRepoForTest()
submittedSimpleReview, err := Get(repo, repository.TestCommitB)
if err != nil {
t.Fatal(err)
}
submittedSimpleReviewBase, err := submittedSimpleReview.GetBaseCommit()
if err != nil {
t.Fatal("Unable to compute the base commit for a known review of a simple commit: ", err)
}
if submittedSimpleReviewBase != repository.TestCommitA {
t.Fatal("Unexpected base commit computed for a known review of a simple commit.")
}
submittedMergeReview, err := Get(repo, repository.TestCommitD)
if err != nil {
t.Fatal(err)
}
submittedMergeReviewBase, err := submittedMergeReview.GetBaseCommit()
if err != nil {
t.Fatal("Unable to compute the base commit for a known review of a merge commit: ", err)
}
if submittedMergeReviewBase != repository.TestCommitC {
t.Fatal("Unexpected base commit computed for a known review of a merge commit.")
}
pendingReview, err := Get(repo, repository.TestCommitG)
if err != nil {
t.Fatal(err)
}
pendingReviewBase, err := pendingReview.GetBaseCommit()
if err != nil {
t.Fatal("Unable to compute the base commit for a known review of a merge commit: ", err)
}
if pendingReviewBase != repository.TestCommitF {
t.Fatal("Unexpected base commit computed for a pending review.")
}
abandonRequest := pendingReview.Request
abandonRequest.TargetRef = ""
abandonNote, err := abandonRequest.Write()
if err != nil {
t.Fatal(err)
}
if err := repo.AppendNote(request.Ref, repository.TestCommitG, abandonNote); err != nil {
t.Fatal(err)
}
abandonedReview, err := Get(repo, repository.TestCommitG)
if err != nil {
t.Fatal(err)
}
if abandonedReview.IsOpen() {
t.Fatal("Failed to update a review to be abandoned")
}
abandonedReviewBase, err := abandonedReview.GetBaseCommit()
if err != nil {
t.Fatal("Unable to compute the base commit for an abandoned review: ", err)
}
if abandonedReviewBase != repository.TestCommitE {
t.Fatal("Unexpected base commit computed for an abandoned review.")
}
}
func TestGetRequests(t *testing.T) {
repo := repository.NewMockRepoForTest()
pendingReview, err := Get(repo, repository.TestCommitG)
if err != nil {
t.Fatal(err)
}
if len(pendingReview.AllRequests) != 3 || pendingReview.Request.Description != "Final description of G" {
t.Fatal("Unexpected requests for a pending review: ", pendingReview.AllRequests, pendingReview.Request)
}
}
func TestRebase(t *testing.T) {
repo := repository.NewMockRepoForTest()
pendingReview, err := Get(repo, repository.TestCommitG)
if err != nil {
t.Fatal(err)
}
// Rebase the review and then confirm that it has been updated correctly.
if err := pendingReview.Rebase(true); err != nil {
t.Fatal(err)
}
reviewJSON, err := pendingReview.GetJSON()
if err != nil {
t.Fatal(err)
}
headRef, err := repo.GetHeadRef()
if err != nil {
t.Fatal(err)
}
if headRef != pendingReview.Request.ReviewRef {
t.Fatal("Failed to switch to the review ref during a rebase")
}
isAncestor, err := repo.IsAncestor(pendingReview.Revision, archiveRef)
if err != nil {
t.Fatal(err)
}
if !isAncestor {
t.Fatalf("Commit %q is not archived", pendingReview.Revision)
}
reviewCommit, err := repo.GetCommitHash(pendingReview.Request.ReviewRef)
if err != nil {
t.Fatal(err)
}
reviewAlias := pendingReview.Request.Alias
if reviewAlias == "" || reviewAlias == pendingReview.Revision || reviewCommit != reviewAlias {
t.Fatalf("Failed to set the review alias: %q", reviewJSON)
}
// Submit the review.
if err := repo.SwitchToRef(pendingReview.Request.TargetRef); err != nil {
t.Fatal(err)
}
if err := repo.MergeRef(pendingReview.Request.ReviewRef, true); err != nil {
t.Fatal(err)
}
// Reread the review and confirm that it has been submitted.
submittedReview, err := Get(repo, pendingReview.Revision)
if err != nil {
t.Fatal(err)
}
submittedReviewJSON, err := submittedReview.GetJSON()
if err != nil {
t.Fatal(err)
}
if !submittedReview.Submitted {
t.Fatalf("Failed to submit the review: %q", submittedReviewJSON)
}
}
func TestRebaseDetachedHead(t *testing.T) {
repo := repository.NewMockRepoForTest()
pendingReview, err := Get(repo, repository.TestCommitG)
if err != nil {
t.Fatal(err)
}
// Switch the review to having a review ref that is not a branch.
pendingReview.Request.ReviewRef = repository.TestAlternateReviewRef
newNote, err := pendingReview.Request.Write()
if err != nil {
t.Fatal(err)
}
if err := repo.AppendNote(request.Ref, pendingReview.Revision, newNote); err != nil {
t.Fatal(err)
}
pendingReview, err = Get(repo, repository.TestCommitG)
if err != nil {
t.Fatal(err)
}
// Rebase the review and then confirm that it has been updated correctly.
if err := pendingReview.Rebase(true); err != nil {
t.Fatal(err)
}
headRef, err := repo.GetHeadRef()
if err != nil {
t.Fatal(err)
}
if headRef != pendingReview.Request.Alias {
t.Fatal("Failed to switch to a detached head during a rebase")
}
isAncestor, err := repo.IsAncestor(pendingReview.Revision, archiveRef)
if err != nil {
t.Fatal(err)
}
if !isAncestor {
t.Fatalf("Commit %q is not archived", pendingReview.Revision)
}
// Submit the review.
if err := repo.SwitchToRef(pendingReview.Request.TargetRef); err != nil {
t.Fatal(err)
}
reviewHead, err := pendingReview.GetHeadCommit()
if err != nil {
t.Fatal(err)
}
if err := repo.MergeRef(reviewHead, true); err != nil {
t.Fatal(err)
}
// Reread the review and confirm that it has been submitted.
submittedReview, err := Get(repo, pendingReview.Revision)
if err != nil {
t.Fatal(err)
}
submittedReviewJSON, err := submittedReview.GetJSON()
if err != nil {
t.Fatal(err)
}
if !submittedReview.Submitted {
t.Fatalf("Failed to submit the review: %q", submittedReviewJSON)
}
}