Move some common gerrit logic into the gerrit package

Moves some of the MultiPart cl handling and the cl log diffing
logic out of devtools/presubmit so it can be used separately.

MultiPart: 2/2
Change-Id: Ia1bcb71b552ad317f00238489beb8aaf22536dad
diff --git a/presubmit/.api b/presubmit/.api
deleted file mode 100644
index 3824416..0000000
--- a/presubmit/.api
+++ /dev/null
@@ -1 +0,0 @@
-pkg main, func NewMultiPartCLSet() *multiPartCLSet
diff --git a/presubmit/common.go b/presubmit/common.go
index 312b720..96f7e31 100644
--- a/presubmit/common.go
+++ b/presubmit/common.go
@@ -10,6 +10,7 @@
 	"sort"
 
 	"v.io/jiri"
+	"v.io/jiri/gerrit"
 	"v.io/x/devtools/internal/test"
 )
 
@@ -82,10 +83,10 @@
 // getSubmittableCLs extracts CLs that have the AutoSubmit label in the commit
 // message and satisfy all the submit rules. If a CL is part of a multi-part CLs
 // set, all the CLs in that set need to be submittable. It returns a list of
-// clLists each of which is either a single CL or a multi-part CLs set.
-func getSubmittableCLs(jirix *jiri.X, cls clList) []clList {
-	submittableCLs := []clList{}
-	multiPartCLs := map[string]*multiPartCLSet{}
+// CLLists each of which is either a single CL or a multi-part CLs set.
+func getSubmittableCLs(jirix *jiri.X, cls gerrit.CLList) []gerrit.CLList {
+	submittableCLs := []gerrit.CLList{}
+	multiPartCLs := map[string]*gerrit.MultiPartCLSet{}
 	for _, cl := range cls {
 		// Check whether a CL satisfies all the submit rules. We do this by checking
 		// the states of all its labels.
@@ -124,11 +125,11 @@
 			if cl.MultiPart != nil {
 				topic := cl.MultiPart.Topic
 				if _, ok := multiPartCLs[topic]; !ok {
-					multiPartCLs[topic] = NewMultiPartCLSet()
+					multiPartCLs[topic] = gerrit.NewMultiPartCLSet()
 				}
-				multiPartCLs[topic].addCL(cl)
+				multiPartCLs[topic].AddCL(cl)
 			} else {
-				submittableCLs = append(submittableCLs, clList{cl})
+				submittableCLs = append(submittableCLs, gerrit.CLList{cl})
 			}
 		}
 	}
@@ -143,8 +144,8 @@
 	// Find complete multi part cl sets.
 	for _, topic := range sortedTopics {
 		set := multiPartCLs[topic]
-		if set.complete() {
-			submittableCLs = append(submittableCLs, set.cls())
+		if set.Complete() {
+			submittableCLs = append(submittableCLs, set.CLs())
 		}
 	}
 
@@ -152,7 +153,7 @@
 }
 
 // submitCLs submits the given CLs.
-func submitCLs(jirix *jiri.X, cls clList) error {
+func submitCLs(jirix *jiri.X, cls gerrit.CLList) error {
 	for _, cl := range cls {
 		curRef := cl.Reference()
 		msg := fmt.Sprintf("submit CL: %s\n", curRef)
diff --git a/presubmit/common_test.go b/presubmit/common_test.go
index f0bba3a..bc9538d 100644
--- a/presubmit/common_test.go
+++ b/presubmit/common_test.go
@@ -28,7 +28,7 @@
 	jirix, cleanup := jiritest.NewX(t)
 	defer cleanup()
 
-	cls := clList{
+	cls := gerrit.CLList{
 		// cls[0]:
 		// CL without AutoSubmit label.
 		gerrit.Change{},
@@ -196,37 +196,37 @@
 		},
 	}
 	testCases := []struct {
-		cls                    clList
-		expectedSubmittableCLs []clList
+		cls                    gerrit.CLList
+		expectedSubmittableCLs []gerrit.CLList
 	}{
 		// Test non-multipart CLs.
 		{
-			cls: clList{cls[0], cls[1], cls[2], cls[3], cls[4], cls[5]},
-			expectedSubmittableCLs: []clList{clList{cls[4]}, clList{cls[5]}},
+			cls: gerrit.CLList{cls[0], cls[1], cls[2], cls[3], cls[4], cls[5]},
+			expectedSubmittableCLs: []gerrit.CLList{gerrit.CLList{cls[4]}, gerrit.CLList{cls[5]}},
 		},
 		// Test multi-part CLs with one of them being unsubmittable.
 		{
-			cls: clList{cls[6], cls[7]},
-			expectedSubmittableCLs: []clList{},
+			cls: gerrit.CLList{cls[6], cls[7]},
+			expectedSubmittableCLs: []gerrit.CLList{},
 		},
 		// Test multi-part CLs where all CLs are submittable.
 		{
-			cls: clList{cls[6], cls[8]},
-			expectedSubmittableCLs: []clList{clList{cls[6], cls[8]}},
+			cls: gerrit.CLList{cls[6], cls[8]},
+			expectedSubmittableCLs: []gerrit.CLList{gerrit.CLList{cls[6], cls[8]}},
 		},
 		// Test multiple submittable multi-part CLs.
 		{
-			cls: clList{cls[6], cls[8], cls[9], cls[10]},
-			expectedSubmittableCLs: []clList{clList{cls[6], cls[8]}, clList{cls[9], cls[10]}},
+			cls: gerrit.CLList{cls[6], cls[8], cls[9], cls[10]},
+			expectedSubmittableCLs: []gerrit.CLList{gerrit.CLList{cls[6], cls[8]}, gerrit.CLList{cls[9], cls[10]}},
 		},
 		// Mixed CLs.
 		{
-			cls: clList{cls[0], cls[4], cls[5], cls[6], cls[7]},
-			expectedSubmittableCLs: []clList{clList{cls[4]}, clList{cls[5]}},
+			cls: gerrit.CLList{cls[0], cls[4], cls[5], cls[6], cls[7]},
+			expectedSubmittableCLs: []gerrit.CLList{gerrit.CLList{cls[4]}, gerrit.CLList{cls[5]}},
 		},
 		{
-			cls: clList{cls[0], cls[4], cls[5], cls[6], cls[8], cls[9], cls[10]},
-			expectedSubmittableCLs: []clList{clList{cls[4]}, clList{cls[5]}, clList{cls[6], cls[8]}, clList{cls[9], cls[10]}},
+			cls: gerrit.CLList{cls[0], cls[4], cls[5], cls[6], cls[8], cls[9], cls[10]},
+			expectedSubmittableCLs: []gerrit.CLList{gerrit.CLList{cls[4]}, gerrit.CLList{cls[5]}, gerrit.CLList{cls[6], cls[8]}, gerrit.CLList{cls[9], cls[10]}},
 		},
 	}
 	for index, test := range testCases {
diff --git a/presubmit/query.go b/presubmit/query.go
index 6477014..3a072e6 100644
--- a/presubmit/query.go
+++ b/presubmit/query.go
@@ -5,9 +5,7 @@
 package main
 
 import (
-	"encoding/json"
 	"fmt"
-	"io/ioutil"
 	"net/url"
 	"os"
 	"reflect"
@@ -19,7 +17,6 @@
 	"v.io/jiri/collect"
 	"v.io/jiri/gerrit"
 	"v.io/jiri/project"
-	"v.io/jiri/runutil"
 	"v.io/jiri/tool"
 	"v.io/x/devtools/tooldata"
 	"v.io/x/lib/cmdline"
@@ -47,74 +44,9 @@
 	tool.InitializeProjectFlags(&cmdQuery.Flags)
 }
 
-type clList []gerrit.Change
-
-// clRefMap indexes cls by their ref strings.
-type clRefMap map[string]gerrit.Change
-
 // clNumberToPatchsetMap is a map from CL numbers to the latest patchset of the CL.
 type clNumberToPatchsetMap map[int]int
 
-// multiPartCLSet represents a set of CLs that spans multiple projects.
-type multiPartCLSet struct {
-	parts         map[int]gerrit.Change // Indexed by cl's part index.
-	expectedTotal int
-	expectedTopic string
-}
-
-// NewMultiPartCLSet creates a new instance of multiPartCLSet.
-func NewMultiPartCLSet() *multiPartCLSet {
-	return &multiPartCLSet{
-		parts:         map[int]gerrit.Change{},
-		expectedTotal: -1,
-		expectedTopic: "",
-	}
-}
-
-// addCL adds a CL to the set after it passes a series of checks.
-func (s *multiPartCLSet) addCL(cl gerrit.Change) error {
-	if cl.MultiPart == nil {
-		return fmt.Errorf("no multi part info found: %#v", cl)
-	}
-	multiPartInfo := cl.MultiPart
-	if s.expectedTotal < 0 {
-		s.expectedTotal = multiPartInfo.Total
-	}
-	if s.expectedTopic == "" {
-		s.expectedTopic = multiPartInfo.Topic
-	}
-	if s.expectedTotal != multiPartInfo.Total {
-		return fmt.Errorf("inconsistent total number of cls in this set: want %d, got %d", s.expectedTotal, multiPartInfo.Total)
-	}
-	if s.expectedTopic != multiPartInfo.Topic {
-		return fmt.Errorf("inconsistent cl topics in this set: want %s, got %s", s.expectedTopic, multiPartInfo.Topic)
-	}
-	if existingCL, ok := s.parts[multiPartInfo.Index]; ok {
-		return fmt.Errorf("duplicated cl part %d found:\ncl to add: %v\nexisting cl:%v", multiPartInfo.Index, cl, existingCL)
-	}
-	s.parts[multiPartInfo.Index] = cl
-	return nil
-}
-
-// complete returns whether the current set has all the cl parts it needs.
-func (s *multiPartCLSet) complete() bool {
-	return len(s.parts) == s.expectedTotal
-}
-
-// cls returns a list of CLs in this set sorted by their part number.
-func (s *multiPartCLSet) cls() clList {
-	ret := clList{}
-	sortedKeys := []int{}
-	for part := range s.parts {
-		sortedKeys = append(sortedKeys, part)
-	}
-	sort.Ints(sortedKeys)
-	for _, part := range sortedKeys {
-		ret = append(ret, s.parts[part])
-	}
-	return ret
-}
-
 // cmdQuery represents the 'query' command of the presubmit tool.
 var cmdQuery = &cmdline.Command{
 	Name:  "query",
@@ -154,7 +86,7 @@
 	}
 
 	// Read previous CLs from the log file.
-	prevCLsMap, err := readLog()
+	prevCLsMap, err := gerrit.ReadLog(logFilePathFlag)
 	if err != nil {
 		return err
 	}
@@ -170,7 +102,7 @@
 	}
 
 	// Write current CLs to the log file.
-	err = writeLog(jirix, curCLs)
+	err = gerrit.WriteLog(logFilePathFlag, curCLs)
 	if err != nil {
 		return err
 	}
@@ -188,7 +120,19 @@
 	}
 
 	// Get new clLists.
-	newCLLists := newOpenCLs(jirix, prevCLsMap, curCLs)
+	newCLLists, multiPartErrs := gerrit.NewOpenCLs(prevCLsMap, curCLs)
+	for _, e := range multiPartErrs {
+		printf(jirix.Stderr(), "%v\n", e)
+
+		// Post multi-part errors to gerrit.
+		if mpErr, ok := e.(*gerrit.ChangeError); ok {
+			clRef := mpErr.CL.Reference()
+			msg := fmt.Sprintf("failed to process multi-part CL %s:\n%v\n", clRef, mpErr.Err)
+			if postErr := postMessage(jirix, msg, []string{clRef}, false); postErr != nil {
+				printf(jirix.Stderr(), "%v\n", postErr)
+			}
+		}
+	}
 
 	// Send the new open CLs one by one to the given Jenkins
 	// project to run presubmit-test builds.
@@ -223,134 +167,12 @@
 	return nil
 }
 
-// readLog returns CLs indexed by thier refs stored in the log file.
-func readLog() (clRefMap, error) {
-	results := clRefMap{}
-	path := logFilePathFlag
-	bytes, err := ioutil.ReadFile(path)
-	if err != nil {
-		if runutil.IsNotExist(err) {
-			return results, nil
-		}
-		return nil, fmt.Errorf("ReadFile(%q) failed: %v", path, err)
-	}
-
-	if err := json.Unmarshal(bytes, &results); err != nil {
-		return nil, fmt.Errorf("Unmarshal failed: %v\n%v", err, string(bytes))
-	}
-	return results, nil
-}
-
-// writeLog writes the refs of the given CLs to the log file.
-func writeLog(jirix *jiri.X, cls clList) (e error) {
-	// Index CLs with their refs.
-	results := clRefMap{}
-	for _, cl := range cls {
-		results[cl.Reference()] = cl
-	}
-	path := logFilePathFlag
-	fd, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
-	if err != nil {
-		return fmt.Errorf("OpenFile(%q) failed: %v", path, err)
-	}
-	defer collect.Error(func() error { return fd.Close() }, &e)
-
-	bytes, err := json.MarshalIndent(results, "", "  ")
-	if err != nil {
-		return fmt.Errorf("MarshalIndent(%v) failed: %v", results, err)
-	}
-	if err := jirix.NewSeq().WriteFile(path, bytes, os.FileMode(0644)).Done(); err != nil {
-		return fmt.Errorf("WriteFile(%q) failed: %v", path, err)
-	}
-	return nil
-}
-
-// newOpenCLs returns a slice of clLists that are "newer" relative to the
-// previous query. A clList is newer if one of the following condition holds:
-// - If a clList has only one cl, then it is newer if:
-//   * Its ref string cannot be found among the CLs from the previous query.
-//
-//   For example: from the previous query, we got cl 1000/1 (cl number 1000 and
-//   patchset 1). Then clLists [1000/2] and [2000/1] are both newer.
-//
-// - If a clList has multiple CLs, then it is newer if:
-//   * It forms a "consistent" (its CLs have the same topic) and "complete"
-//     (it contains all the parts) multi-part CL set.
-//   * At least one of their ref strings cannot be found in the CLs from the
-//     previous query.
-//
-//   For example: from the previous query, we got cl 3001/1 which is the first
-//   part of a multi part cl set with topic "T1". Suppose the current query
-//   returns cl 3002/1 which is the second part of the same set. In this case,
-//   a clList [3001/1 3002/1] will be returned. Then suppose in the next query,
-//   we got cl 3002/2 which is newer then 3002/1. In this case, a clList
-//   [3001/1 3002/2] will be returned.
-func newOpenCLs(jirix *jiri.X, prevCLsMap clRefMap, curCLs clList) []clList {
-	newCLs := []clList{}
-	topicsInNewCLs := map[string]struct{}{}
-	multiPartCLs := clList{}
-	for _, curCL := range curCLs {
-		// Ref could be empty in cases where a patchset is causing conflicts.
-		if curCL.Reference() == "" {
-			continue
-		}
-		if _, ok := prevCLsMap[curCL.Reference()]; !ok {
-			// This individual cl is newer.
-			if curCL.MultiPart == nil {
-				// This cl is not a multi part cl.
-				// Add it to the return slice.
-				newCLs = append(newCLs, clList{curCL})
-			} else {
-				// This cl is a multi part cl.
-				// Record its topic.
-				topicsInNewCLs[curCL.MultiPart.Topic] = struct{}{}
-			}
-		}
-		// Record all multi part CLs.
-		if curCL.MultiPart != nil {
-			multiPartCLs = append(multiPartCLs, curCL)
-		}
-	}
-
-	// Find complete multi part cl sets.
-	setMap := map[string]*multiPartCLSet{}
-	for _, curCL := range multiPartCLs {
-		multiPartInfo := curCL.MultiPart
-
-		// Skip topics that contain no new CLs.
-		topic := multiPartInfo.Topic
-		if _, ok := topicsInNewCLs[topic]; !ok {
-			continue
-		}
-
-		if _, ok := setMap[topic]; !ok {
-			setMap[topic] = NewMultiPartCLSet()
-		}
-		curSet := setMap[topic]
-		if err := curSet.addCL(curCL); err != nil {
-			curCLRef := curCL.Reference()
-			message := fmt.Sprintf("failed to process multi-part CL %s:\n%v\n", curCLRef, err.Error())
-			if err := postMessage(jirix, message, []string{curCLRef}, false); err != nil {
-				printf(jirix.Stderr(), "%v\n", err)
-			}
-			printf(jirix.Stderr(), "%v\n", err)
-		}
-	}
-	for _, set := range setMap {
-		if set.complete() {
-			newCLs = append(newCLs, set.cls())
-		}
-	}
-
-	return newCLs
-}
-
 type clsSender struct {
-	clLists          []clList
+	clLists          []gerrit.CLList
 	projects         project.Projects
 	clsSent          int
 	removeOutdatedFn func(*jiri.X, clNumberToPatchsetMap) []error
-	addPresubmitFn   func(*jiri.X, clList, []string) error
+	addPresubmitFn   func(*jiri.X, gerrit.CLList, []string) error
 	postMessageFn    func(*jiri.X, string, []string, bool) error
 }
 
@@ -430,17 +252,17 @@
 	hasNonGoogleOwner bool
 	projects          []string
 	refs              []string
-	filteredCLList    clList
+	filteredCLList    gerrit.CLList
 }
 
-func (s *clsSender) processCLList(jirix *jiri.X, curCLList clList) *clListInfo {
+func (s *clsSender) processCLList(jirix *jiri.X, curCLList gerrit.CLList) *clListInfo {
 	curCLMap := clNumberToPatchsetMap{}
 	clStrings := []string{}
 	skipPresubmitTest := false
 	hasNonGoogleOwner := false
 	projects := []string{}
 	refs := []string{}
-	filteredCLList := clList{}
+	filteredCLList := gerrit.CLList{}
 	for _, curCL := range curCLList {
 		// Ignore all CLs that are not in projects identified by the manifestFlag.
 		// TODO(jingjin): find a better way so we can remove this check.
@@ -666,7 +488,7 @@
 
 // addPresubmitTestBuild uses Jenkins' remote access API to add a build for
 // a set of open CLs to run presubmit tests.
-func addPresubmitTestBuild(jirix *jiri.X, cls clList, tests []string) error {
+func addPresubmitTestBuild(jirix *jiri.X, cls gerrit.CLList, tests []string) error {
 	jenkins, err := jirix.Jenkins(jenkinsHostFlag)
 	if err != nil {
 		return err
diff --git a/presubmit/query_test.go b/presubmit/query_test.go
index 6772770..a10075c 100644
--- a/presubmit/query_test.go
+++ b/presubmit/query_test.go
@@ -18,210 +18,6 @@
 	"v.io/x/devtools/tooldata"
 )
 
-func TestMultiPartCLSet(t *testing.T) {
-	set := NewMultiPartCLSet()
-	checkMultiPartCLSet(t, -1, map[int]gerrit.Change{}, set)
-
-	// Add a non-multipart cl.
-	cl := genCL(1000, 1, "relase.go.core")
-	if err := set.addCL(cl); err == nil {
-		t.Fatalf("expected addCL(%v) to fail and it did not", cl)
-	}
-	checkMultiPartCLSet(t, -1, map[int]gerrit.Change{}, set)
-
-	// Add a multi part cl.
-	cl.MultiPart = &gerrit.MultiPartCLInfo{
-		Topic: "test",
-		Index: 1,
-		Total: 2,
-	}
-	if err := set.addCL(cl); err != nil {
-		t.Fatalf("addCL(%v) failed: %v", cl, err)
-	}
-	checkMultiPartCLSet(t, 2, map[int]gerrit.Change{
-		1: cl,
-	}, set)
-
-	// Test incomplete.
-	if expected, got := false, set.complete(); expected != got {
-		t.Fatalf("want %v, got %v", expected, got)
-	}
-
-	// Add another multi part cl with the wrong "Total" number.
-	cl2 := genMultiPartCL(1050, 2, "release.js.core", "test", 2, 3)
-	if err := set.addCL(cl2); err == nil {
-		t.Fatalf("expected addCL(%v) to fail and it did not", cl)
-	}
-	checkMultiPartCLSet(t, 2, map[int]gerrit.Change{
-		1: cl,
-	}, set)
-
-	// Add another multi part cl with duplicated "Index" number.
-	cl3 := genMultiPartCL(1052, 2, "release.js.core", "Test", 1, 2)
-	if err := set.addCL(cl3); err == nil {
-		t.Fatalf("expected addCL(%v) to fail and it did not", cl)
-	}
-	checkMultiPartCLSet(t, 2, map[int]gerrit.Change{
-		1: cl,
-	}, set)
-
-	// Add another multi part cl with the wrong "Topic".
-	cl4 := genMultiPartCL(1062, 2, "release.js.core", "test123", 1, 2)
-	if err := set.addCL(cl4); err == nil {
-		t.Fatalf("expected addCL(%v) to fail and it did not", cl)
-	}
-	checkMultiPartCLSet(t, 2, map[int]gerrit.Change{
-		1: cl,
-	}, set)
-
-	// Add a valid multi part cl.
-	cl5 := genMultiPartCL(1072, 2, "release.js.core", "test", 2, 2)
-	if err := set.addCL(cl5); err != nil {
-		t.Fatalf("addCL(%v) failed: %v", cl, err)
-	}
-	checkMultiPartCLSet(t, 2, map[int]gerrit.Change{
-		1: cl,
-		2: cl5,
-	}, set)
-
-	// Test complete.
-	if expected, got := true, set.complete(); expected != got {
-		t.Fatalf("want %v, got %v", expected, got)
-	}
-
-	// Test cls.
-	if expected, got := (clList{cl, cl5}), set.cls(); !reflect.DeepEqual(expected, got) {
-		t.Fatalf("want %v, got %v", expected, got)
-	}
-}
-
-func checkMultiPartCLSet(t *testing.T, expectedTotal int, expectedCLsByPart map[int]gerrit.Change, set *multiPartCLSet) {
-	if expectedTotal != set.expectedTotal {
-		t.Fatalf("total: want %v, got %v", expectedTotal, set.expectedTotal)
-	}
-	if !reflect.DeepEqual(expectedCLsByPart, set.parts) {
-		t.Fatalf("clsByPart: want %+v, got %+v", expectedCLsByPart, set.parts)
-	}
-}
-
-func TestNewOpenCLs(t *testing.T) {
-	jirix, cleanup := jiritest.NewX(t)
-	defer cleanup()
-	nonMultiPartCLs := clList{
-		genCL(1010, 1, "release.go.core"),
-		genCL(1020, 2, "release.go.tools"),
-		genCL(1030, 3, "release.js.core"),
-
-		genMultiPartCL(1000, 1, "release.js.core", "T1", 1, 2),
-		genMultiPartCL(1001, 1, "release.go.core", "T1", 2, 2),
-		genMultiPartCL(1002, 2, "release.go.core", "T2", 2, 2),
-		genMultiPartCL(1001, 2, "release.go.core", "T1", 2, 2),
-	}
-	multiPartCLs := clList{
-		// Multi part CLs.
-		// The first two form a complete set for topic T1.
-		// The third one looks like the second one, but has a different topic.
-		// The last one has a larger patchset than the second one.
-		genMultiPartCL(1000, 1, "release.js.core", "T1", 1, 2),
-		genMultiPartCL(1001, 1, "release.go.core", "T1", 2, 2),
-		genMultiPartCL(1002, 2, "release.go.core", "T2", 2, 2),
-		genMultiPartCL(1001, 2, "release.go.core", "T1", 2, 2),
-	}
-
-	type testCase struct {
-		prevCLsMap clRefMap
-		curCLs     clList
-		expected   []clList
-	}
-	testCases := []testCase{
-		////////////////////////////////
-		// Tests for non-multipart CLs.
-
-		// Both prevCLsMap and curCLs are empty.
-		testCase{
-			prevCLsMap: clRefMap{},
-			curCLs:     clList{},
-			expected:   []clList{},
-		},
-		// prevCLsMap is empty, curCLs is not.
-		testCase{
-			prevCLsMap: clRefMap{},
-			curCLs:     clList{nonMultiPartCLs[0], nonMultiPartCLs[1]},
-			expected:   []clList{clList{nonMultiPartCLs[0]}, clList{nonMultiPartCLs[1]}},
-		},
-		// prevCLsMap is not empty, curCLs is.
-		testCase{
-			prevCLsMap: clRefMap{nonMultiPartCLs[0].Reference(): nonMultiPartCLs[0]},
-			curCLs:     clList{},
-			expected:   []clList{},
-		},
-		// prevCLsMap and curCLs are not empty, and they have overlapping refs.
-		testCase{
-			prevCLsMap: clRefMap{
-				nonMultiPartCLs[0].Reference(): nonMultiPartCLs[0],
-				nonMultiPartCLs[1].Reference(): nonMultiPartCLs[1],
-			},
-			curCLs:   clList{nonMultiPartCLs[1], nonMultiPartCLs[2]},
-			expected: []clList{clList{nonMultiPartCLs[2]}},
-		},
-		// prevCLsMap and curCLs are not empty, and they have NO overlapping refs.
-		testCase{
-			prevCLsMap: clRefMap{nonMultiPartCLs[0].Reference(): nonMultiPartCLs[0]},
-			curCLs:     clList{nonMultiPartCLs[1]},
-			expected:   []clList{clList{nonMultiPartCLs[1]}},
-		},
-
-		////////////////////////////////
-		// Tests for multi part CLs.
-
-		// len(curCLs) > len(prevCLsMap).
-		// And the CLs in curCLs have different topics.
-		testCase{
-			prevCLsMap: clRefMap{multiPartCLs[0].Reference(): multiPartCLs[0]},
-			curCLs:     clList{multiPartCLs[0], multiPartCLs[2]},
-			expected:   []clList{},
-		},
-		// len(curCLs) > len(prevCLsMap).
-		// And the CLs in curCLs form a complete multi part cls set.
-		testCase{
-			prevCLsMap: clRefMap{multiPartCLs[0].Reference(): multiPartCLs[0]},
-			curCLs:     clList{multiPartCLs[0], multiPartCLs[1]},
-			expected:   []clList{clList{multiPartCLs[0], multiPartCLs[1]}},
-		},
-		// len(curCLs) == len(prevCLsMap).
-		// And cl[6] has a larger patchset than multiPartCLs[4] with identical cl number.
-		testCase{
-			prevCLsMap: clRefMap{
-				multiPartCLs[0].Reference(): multiPartCLs[0],
-				multiPartCLs[1].Reference(): multiPartCLs[1],
-			},
-			curCLs:   clList{multiPartCLs[0], multiPartCLs[3]},
-			expected: []clList{clList{multiPartCLs[0], multiPartCLs[3]}},
-		},
-
-		////////////////////////////////
-		// Tests for mixed.
-		testCase{
-			prevCLsMap: clRefMap{
-				multiPartCLs[0].Reference(): multiPartCLs[0],
-				multiPartCLs[1].Reference(): multiPartCLs[1],
-			},
-			curCLs: clList{nonMultiPartCLs[0], multiPartCLs[0], multiPartCLs[3]},
-			expected: []clList{
-				clList{nonMultiPartCLs[0]},
-				clList{multiPartCLs[0], multiPartCLs[3]},
-			},
-		},
-	}
-
-	for index, test := range testCases {
-		got := newOpenCLs(jirix, test.prevCLsMap, test.curCLs)
-		if !reflect.DeepEqual(test.expected, got) {
-			t.Fatalf("case %d: want: %v, got: %v", index, test.expected, got)
-		}
-	}
-}
-
 func TestSendCLListsToPresubmitTest(t *testing.T) {
 	fake, cleanup := jiritest.NewFakeJiriRoot(t)
 	defer cleanup()
@@ -239,31 +35,31 @@
 		t.Fatalf("%v", err)
 	}
 
-	clLists := []clList{
-		clList{
-			genCL(1000, 1, "release.js.core"),
+	clLists := []gerrit.CLList{
+		gerrit.CLList{
+			gerrit.GenCL(1000, 1, "release.js.core"),
 		},
-		clList{
-			genCLWithMoreData(2000, 1, "release.js.core", gerrit.PresubmitTestTypeNone, "vj@google.com"),
+		gerrit.CLList{
+			gerrit.GenCLWithMoreData(2000, 1, "release.js.core", gerrit.PresubmitTestTypeNone, "vj@google.com"),
 		},
-		clList{
-			genCLWithMoreData(2010, 1, "release.js.core", gerrit.PresubmitTestTypeAll, "foo@bar.com"),
+		gerrit.CLList{
+			gerrit.GenCLWithMoreData(2010, 1, "release.js.core", gerrit.PresubmitTestTypeAll, "foo@bar.com"),
 		},
-		clList{
-			genMultiPartCL(1001, 1, "release.js.core", "t", 1, 2),
-			genMultiPartCL(1002, 1, "release.go.core", "t", 2, 2),
+		gerrit.CLList{
+			gerrit.GenMultiPartCL(1001, 1, "release.js.core", "t", 1, 2),
+			gerrit.GenMultiPartCL(1002, 1, "release.go.core", "t", 2, 2),
 		},
-		clList{
-			genMultiPartCL(1003, 1, "release.js.core", "t", 1, 3),
-			genMultiPartCL(1004, 1, "release.go.core", "t", 2, 3),
-			genMultiPartCLWithMoreData(1005, 1, "release.go.core", "t", 3, 3, "foo@bar.com"),
+		gerrit.CLList{
+			gerrit.GenMultiPartCL(1003, 1, "release.js.core", "t", 1, 3),
+			gerrit.GenMultiPartCL(1004, 1, "release.go.core", "t", 2, 3),
+			gerrit.GenMultiPartCLWithMoreData(1005, 1, "release.go.core", "t", 3, 3, "foo@bar.com"),
 		},
-		clList{
-			genCL(3000, 1, "non-existent-project"),
+		gerrit.CLList{
+			gerrit.GenCL(3000, 1, "non-existent-project"),
 		},
-		clList{
-			genMultiPartCL(1005, 1, "release.js.core", "t", 1, 2),
-			genMultiPartCL(1006, 1, "non-existent-project", "t", 2, 2),
+		gerrit.CLList{
+			gerrit.GenMultiPartCL(1005, 1, "release.js.core", "t", 1, 2),
+			gerrit.GenMultiPartCL(1006, 1, "non-existent-project", "t", 2, 2),
 		},
 	}
 
@@ -283,7 +79,7 @@
 
 		// Mock out the addPresubmitTestBuild function.
 		// It will return error for the first clList.
-		addPresubmitFn: func(jirix *jiri.X, cls clList, tests []string) error {
+		addPresubmitFn: func(jirix *jiri.X, cls gerrit.CLList, tests []string) error {
 			if reflect.DeepEqual(cls, clLists[0]) {
 				return fmt.Errorf("err")
 			} else {
@@ -481,58 +277,3 @@
 		}
 	}
 }
-
-func genCL(clNumber, patchset int, project string) gerrit.Change {
-	return genCLWithMoreData(clNumber, patchset, project, gerrit.PresubmitTestTypeAll, "vj@google.com")
-}
-
-func genCLWithMoreData(clNumber, patchset int, project string, presubmit gerrit.PresubmitTestType, ownerEmail string) gerrit.Change {
-	change := gerrit.Change{
-		Current_revision: "r",
-		Revisions: gerrit.Revisions{
-			"r": gerrit.Revision{
-				Fetch: gerrit.Fetch{
-					Http: gerrit.Http{
-						Ref: fmt.Sprintf("refs/changes/xx/%d/%d", clNumber, patchset),
-					},
-				},
-			},
-		},
-		Project:       project,
-		Change_id:     "",
-		PresubmitTest: presubmit,
-		Owner: gerrit.Owner{
-			Email: ownerEmail,
-		},
-	}
-	return change
-}
-
-func genMultiPartCL(clNumber, patchset int, project, topic string, index, total int) gerrit.Change {
-	return genMultiPartCLWithMoreData(clNumber, patchset, project, topic, index, total, "vj@google.com")
-}
-
-func genMultiPartCLWithMoreData(clNumber, patchset int, project, topic string, index, total int, ownerEmail string) gerrit.Change {
-	return gerrit.Change{
-		Current_revision: "r",
-		Revisions: gerrit.Revisions{
-			"r": gerrit.Revision{
-				Fetch: gerrit.Fetch{
-					Http: gerrit.Http{
-						Ref: fmt.Sprintf("refs/changes/xx/%d/%d", clNumber, patchset),
-					},
-				},
-			},
-		},
-		Project:   project,
-		Change_id: "",
-		Owner: gerrit.Owner{
-			Email: ownerEmail,
-		},
-		MultiPart: &gerrit.MultiPartCLInfo{
-			Topic: topic,
-			Index: index,
-			Total: total,
-		},
-	}
-}