Reimplement DepSet as a wrapper around a generic implementation

Implement depSet as a generic depsets implementation using reflection,
and then make DepSet a type-safe wrapper around it.  This will allow
additional wrappers for depsets that work with other types.  All of
this can be replaced with generics once Go supports them.

Test: depset_test.go
Change-Id: Id9df17bcc76f6c1545e7eb498f298066cf8a7679
diff --git a/android/depset_test.go b/android/depset_test.go
index c328127..955ccb0 100644
--- a/android/depset_test.go
+++ b/android/depset_test.go
@@ -17,6 +17,7 @@
 import (
 	"fmt"
 	"reflect"
+	"strconv"
 	"strings"
 	"testing"
 )
@@ -108,6 +109,7 @@
 			name: "builderReuse",
 			depSet: func(t *testing.T, order DepSetOrder) *DepSet {
 				assertEquals := func(t *testing.T, w, g Paths) {
+					t.Helper()
 					if !reflect.DeepEqual(w, g) {
 						t.Errorf("want %q, got %q", w, g)
 					}
@@ -302,3 +304,87 @@
 		})
 	}
 }
+
+func Test_firstUnique(t *testing.T) {
+	f := func(t *testing.T, imp func([]string) []string, in, want []string) {
+		t.Helper()
+		out := imp(in)
+		if !reflect.DeepEqual(out, want) {
+			t.Errorf("incorrect output:")
+			t.Errorf("     input: %#v", in)
+			t.Errorf("  expected: %#v", want)
+			t.Errorf("       got: %#v", out)
+		}
+	}
+
+	for _, testCase := range firstUniqueStringsTestCases {
+		t.Run("list", func(t *testing.T) {
+			f(t, func(s []string) []string {
+				return firstUniqueList(s).([]string)
+			}, testCase.in, testCase.out)
+		})
+		t.Run("map", func(t *testing.T) {
+			f(t, func(s []string) []string {
+				return firstUniqueMap(s).([]string)
+			}, testCase.in, testCase.out)
+		})
+	}
+}
+
+func Benchmark_firstUnique(b *testing.B) {
+	implementations := []struct {
+		name string
+		f    func([]string) []string
+	}{
+		{
+			name: "list",
+			f: func(slice []string) []string {
+				return firstUniqueList(slice).([]string)
+			},
+		},
+		{
+			name: "map",
+			f: func(slice []string) []string {
+				return firstUniqueMap(slice).([]string)
+			},
+		},
+		{
+			name: "optimal",
+			f: func(slice []string) []string {
+				return firstUnique(slice).([]string)
+			},
+		},
+	}
+	const maxSize = 1024
+	uniqueStrings := make([]string, maxSize)
+	for i := range uniqueStrings {
+		uniqueStrings[i] = strconv.Itoa(i)
+	}
+	sameString := make([]string, maxSize)
+	for i := range sameString {
+		sameString[i] = uniqueStrings[0]
+	}
+
+	f := func(b *testing.B, imp func([]string) []string, s []string) {
+		for i := 0; i < b.N; i++ {
+			b.ReportAllocs()
+			s = append([]string(nil), s...)
+			imp(s)
+		}
+	}
+
+	for n := 1; n <= maxSize; n <<= 1 {
+		b.Run(strconv.Itoa(n), func(b *testing.B) {
+			for _, implementation := range implementations {
+				b.Run(implementation.name, func(b *testing.B) {
+					b.Run("same", func(b *testing.B) {
+						f(b, implementation.f, sameString[:n])
+					})
+					b.Run("unique", func(b *testing.B) {
+						f(b, implementation.f, uniqueStrings[:n])
+					})
+				})
+			}
+		})
+	}
+}