secure/precis: fix compare order of operations

Reduce the number of heap allocations required by the compare operation,
and factor out enforce and compare transformations into a single
function.

Also throw in a minor typo fix in a comment.

Change-Id: I8e9fec1fc51d38a1950f95be72e07fb80d9949e3
Reviewed-on: https://go-review.googlesource.com/29966
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/secure/precis/compare_test.go b/secure/precis/compare_test.go
new file mode 100644
index 0000000..d709d74
--- /dev/null
+++ b/secure/precis/compare_test.go
@@ -0,0 +1,53 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package precis
+
+import (
+	"fmt"
+	"testing"
+
+	"golang.org/x/text/internal/testtext"
+)
+
+type compareTestCase struct {
+	a      string
+	b      string
+	result bool
+}
+
+var compareTestCases = []struct {
+	name  string
+	p     *Profile
+	cases []compareTestCase
+}{
+	{"Nickname", Nickname, []compareTestCase{
+		{"a", "b", false},
+		{"  Swan  of   Avon   ", "swan of avon", true},
+		{"Foo", "foo", true},
+		{"foo", "foo", true},
+		{"Foo Bar", "foo bar", true},
+		{"foo bar", "foo bar", true},
+		{"\u03A3", "\u03C3", true},
+		{"\u03A3", "\u03C2", false},
+		{"\u03C3", "\u03C2", false},
+		{"Richard \u2163", "richard iv", true},
+		{"Å", "å", true},
+		{"ff", "ff", true}, // because of NFKC
+		{"ß", "sS", false},
+	}},
+}
+
+func TestCompare(t *testing.T) {
+	for _, g := range compareTestCases {
+		for i, tc := range g.cases {
+			name := fmt.Sprintf("%s:%d:%+q", g.name, i, tc.a)
+			testtext.Run(t, name, func(t *testing.T) {
+				if result := g.p.Compare(tc.a, tc.b); result != tc.result {
+					t.Errorf("got %v; want %v", result, tc.result)
+				}
+			})
+		}
+	}
+}
diff --git a/secure/precis/profile.go b/secure/precis/profile.go
index 5fb74b8..e21f8c6 100644
--- a/secure/precis/profile.go
+++ b/secure/precis/profile.go
@@ -106,14 +106,14 @@
 	return err
 }
 
-func (b *buffers) enforce(p *Profile, src []byte) (str []byte, err error) {
+func (b *buffers) enforce(p *Profile, src []byte, comparing bool) (str []byte, err error) {
 	b.src = src
 
 	// These transforms are applied in the order defined in
 	// https://tools.ietf.org/html/rfc7564#section-7
 
 	// TODO: allow different width transforms options.
-	if p.options.foldWidth {
+	if p.options.foldWidth || (p.options.ignorecase && comparing) {
 		// TODO: use Span, once available.
 		if err = b.apply(width.Fold); err != nil {
 			return nil, err
@@ -129,6 +129,11 @@
 			return nil, err
 		}
 	}
+	if comparing && p.options.ignorecase {
+		if err = b.apply(cases.Lower(language.Und, cases.HandleFinalSigma(false))); err != nil {
+			return nil, err
+		}
+	}
 	if n := p.norm.QuickSpan(b.src); n < len(b.src) {
 		x := b.next & 1
 		n = copy(b.buf[x], b.src[:n])
@@ -171,7 +176,7 @@
 func (p *Profile) Append(dst, src []byte) ([]byte, error) {
 	var buf buffers
 	buf.init(8 + len(src) + len(src)>>2)
-	b, err := buf.enforce(p, src)
+	b, err := buf.enforce(p, src, false)
 	if err != nil {
 		return nil, err
 	}
@@ -182,7 +187,7 @@
 func (p *Profile) Bytes(b []byte) ([]byte, error) {
 	var buf buffers
 	buf.init(8 + len(b) + len(b)>>2)
-	b, err := buf.enforce(p, b)
+	b, err := buf.enforce(p, b, false)
 	if err != nil {
 		return nil, err
 	}
@@ -198,7 +203,7 @@
 func (p *Profile) String(s string) (string, error) {
 	var buf buffers
 	buf.init(8 + len(s) + len(s)>>2)
-	b, err := buf.enforce(p, []byte(s))
+	b, err := buf.enforce(p, []byte(s), false)
 	if err != nil {
 		return "", err
 	}
@@ -209,26 +214,21 @@
 // (byte-for-byte equality). If either string cannot be enforced, the comparison
 // is false.
 func (p *Profile) Compare(a, b string) bool {
-	a, err := p.String(a)
+	var buf buffers
+
+	buf.init(8 + len(a) + len(a)>>2)
+	str, err := buf.enforce(p, []byte(a), true)
 	if err != nil {
 		return false
 	}
-	b, err = p.String(b)
+	a = string(str)
+
+	buf.init(8 + len(b) + len(b)>>2)
+	str, err = buf.enforce(p, []byte(b), true)
 	if err != nil {
 		return false
 	}
-
-	// TODO: This is out of order. Need to extract the transformation logic and
-	// put this in where the normal case folding would go (but only for
-	// comparison).
-	if p.options.ignorecase {
-		a = width.Fold.String(a)
-		b = width.Fold.String(a)
-
-		caser := cases.Lower(language.Und, cases.HandleFinalSigma(false))
-		a = caser.String(a)
-		b = caser.String(b)
-	}
+	b = string(str)
 
 	return a == b
 }
diff --git a/secure/precis/tables_test.go b/secure/precis/tables_test.go
index b4f152c..67f5b40 100644
--- a/secure/precis/tables_test.go
+++ b/secure/precis/tables_test.go
@@ -31,7 +31,7 @@
 	}
 })
 
-// Ensure that ceratain properties were generated correctly.
+// Ensure that certain properties were generated correctly.
 func TestTable(t *testing.T) {
 	tests := []tableTest{
 		tableTest{