|  | // 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 bidirule | 
|  |  | 
|  | import ( | 
|  | "fmt" | 
|  | "testing" | 
|  |  | 
|  | "golang.org/x/text/internal/testtext" | 
|  | "golang.org/x/text/transform" | 
|  | "golang.org/x/text/unicode/bidi" | 
|  | ) | 
|  |  | 
|  | const ( | 
|  | strL   = "ABC"    // Left to right - most letters in LTR scripts | 
|  | strR   = "עברית"  // Right to left - most letters in non-Arabic RTL scripts | 
|  | strAL  = "دبي"    // Arabic letters - most letters in the Arabic script | 
|  | strEN  = "123"    // European Number (0-9, and Extended Arabic-Indic numbers) | 
|  | strES  = "+-"     // European Number Separator (+ and -) | 
|  | strET  = "$"      // European Number Terminator (currency symbols, the hash sign, the percent sign and so on) | 
|  | strAN  = "\u0660" // Arabic Number; this encompasses the Arabic-Indic numbers, but not the Extended Arabic-Indic numbers | 
|  | strCS  = ","      // Common Number Separator (. , / : et al) | 
|  | strNSM = "\u0300" // Nonspacing Mark - most combining accents | 
|  | strBN  = "\u200d" // Boundary Neutral - control characters (ZWNJ, ZWJ, and others) | 
|  | strB   = "\u2029" // Paragraph Separator | 
|  | strS   = "\u0009" // Segment Separator | 
|  | strWS  = " "      // Whitespace, including the SPACE character | 
|  | strON  = "@"      // Other Neutrals, including @, &, parentheses, MIDDLE DOT | 
|  | ) | 
|  |  | 
|  | type ruleTest struct { | 
|  | in  string | 
|  | dir bidi.Direction | 
|  | n   int // position at which the rule fails | 
|  | err error | 
|  |  | 
|  | // For tests that split the string in two. | 
|  | pSrc  int   // number of source bytes to consume first | 
|  | szDst int   // size of destination buffer | 
|  | nSrc  int   // source bytes consumed and bytes written | 
|  | err0  error // error after first run | 
|  | } | 
|  |  | 
|  | var testCases = [][]ruleTest{ | 
|  | // Go-specific rules. | 
|  | // Invalid UTF-8 is invalid. | 
|  | 0: []ruleTest{{ | 
|  | in:  "", | 
|  | dir: bidi.LeftToRight, | 
|  | }, { | 
|  | in:  "\x80", | 
|  | dir: bidi.LeftToRight, | 
|  | err: ErrInvalid, | 
|  | n:   0, | 
|  | }, { | 
|  | in:  "\xcc", | 
|  | dir: bidi.LeftToRight, | 
|  | err: ErrInvalid, | 
|  | n:   0, | 
|  | }, { | 
|  | in:  "abc\x80", | 
|  | dir: bidi.LeftToRight, | 
|  | err: ErrInvalid, | 
|  | n:   3, | 
|  | }, { | 
|  | in:  "abc\xcc", | 
|  | dir: bidi.LeftToRight, | 
|  | err: ErrInvalid, | 
|  | n:   3, | 
|  | }, { | 
|  | in:  "abc\xccdef", | 
|  | dir: bidi.LeftToRight, | 
|  | err: ErrInvalid, | 
|  | n:   3, | 
|  | }, { | 
|  | in:  "\xccdef", | 
|  | dir: bidi.LeftToRight, | 
|  | err: ErrInvalid, | 
|  | n:   0, | 
|  | }, { | 
|  | in:  strR + "\x80", | 
|  | dir: bidi.RightToLeft, | 
|  | err: ErrInvalid, | 
|  | n:   len(strR), | 
|  | }, { | 
|  | in:  strR + "\xcc", | 
|  | dir: bidi.RightToLeft, | 
|  | err: ErrInvalid, | 
|  | n:   len(strR), | 
|  | }, { | 
|  | in:  strAL + "\xcc" + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | err: ErrInvalid, | 
|  | n:   len(strAL), | 
|  | }, { | 
|  | in:  "\xcc" + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | err: ErrInvalid, | 
|  | n:   0, | 
|  | }}, | 
|  |  | 
|  | // Rule 2.1: The first character must be a character with Bidi property L, | 
|  | // R, or AL.  If it has the R or AL property, it is an RTL label; if it has | 
|  | // the L property, it is an LTR label. | 
|  | 1: []ruleTest{{ | 
|  | in:  strL, | 
|  | dir: bidi.LeftToRight, | 
|  | }, { | 
|  | in:  strR, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strAL, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strAN, | 
|  | dir: bidi.RightToLeft, | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strEN, | 
|  | dir: bidi.LeftToRight, | 
|  | err: nil, // not an RTL string | 
|  | }, { | 
|  | in:  strES, | 
|  | dir: bidi.LeftToRight, | 
|  | err: nil, // not an RTL string | 
|  | }, { | 
|  | in:  strET, | 
|  | dir: bidi.LeftToRight, | 
|  | err: nil, // not an RTL string | 
|  | }, { | 
|  | in:  strCS, | 
|  | dir: bidi.LeftToRight, | 
|  | err: nil, // not an RTL string | 
|  | }, { | 
|  | in:  strNSM, | 
|  | dir: bidi.LeftToRight, | 
|  | err: nil, // not an RTL string | 
|  | }, { | 
|  | in:  strBN, | 
|  | dir: bidi.LeftToRight, | 
|  | err: nil, // not an RTL string | 
|  | }, { | 
|  | in:  strB, | 
|  | dir: bidi.LeftToRight, | 
|  | err: nil, // not an RTL string | 
|  | }, { | 
|  | in:  strS, | 
|  | dir: bidi.LeftToRight, | 
|  | err: nil, // not an RTL string | 
|  | }, { | 
|  | in:  strWS, | 
|  | dir: bidi.LeftToRight, | 
|  | err: nil, // not an RTL string | 
|  | }, { | 
|  | in:  strON, | 
|  | dir: bidi.LeftToRight, | 
|  | err: nil, // not an RTL string | 
|  | }, { | 
|  | in:  strEN + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | err: ErrInvalid, | 
|  | n:   3, | 
|  | }, { | 
|  | in:  strES + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | err: ErrInvalid, | 
|  | n:   2, | 
|  | }, { | 
|  | in:  strET + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | err: ErrInvalid, | 
|  | n:   1, | 
|  | }, { | 
|  | in:  strCS + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | err: ErrInvalid, | 
|  | n:   1, | 
|  | }, { | 
|  | in:  strNSM + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | err: ErrInvalid, | 
|  | n:   2, | 
|  | }, { | 
|  | in:  strBN + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | err: ErrInvalid, | 
|  | n:   3, | 
|  | }, { | 
|  | in:  strB + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | err: ErrInvalid, | 
|  | n:   3, | 
|  | }, { | 
|  | in:  strS + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | err: ErrInvalid, | 
|  | n:   1, | 
|  | }, { | 
|  | in:  strWS + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | err: ErrInvalid, | 
|  | n:   1, | 
|  | }, { | 
|  | in:  strON + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | err: ErrInvalid, | 
|  | n:   1, | 
|  | }}, | 
|  |  | 
|  | // Rule 2.2: In an RTL label, only characters with the Bidi properties R, | 
|  | // AL, AN, EN, ES, CS, ET, ON, BN, or NSM are allowed. | 
|  | 2: []ruleTest{{ | 
|  | in:  strR + strR + strAL, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strR + strAL + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strR + strAN + strAL, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strR + strEN + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strR + strES + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strR + strCS + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strR + strET + strAL, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strR + strON + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strR + strBN + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strR + strNSM + strAL, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strR + strL + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strR), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strR + strB + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strR), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strR + strS + strAL, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strR), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strR + strWS + strAL, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strR), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strAL + strR + strAL, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strAL + strAL + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strAL + strAN + strAL, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strAL + strEN + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strAL + strES + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strAL + strCS + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strAL + strET + strAL, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strAL + strON + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strAL + strBN + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strAL + strNSM + strAL, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strAL + strL + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strAL), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strAL + strB + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strAL), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strAL + strS + strAL, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strAL), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strAL + strWS + strAL, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strAL), | 
|  | err: ErrInvalid, | 
|  | }}, | 
|  |  | 
|  | // Rule 2.3: In an RTL label, the end of the label must be a character with | 
|  | // Bidi property R, AL, EN, or AN, followed by zero or more characters with | 
|  | // Bidi property NSM. | 
|  | 3: []ruleTest{{ | 
|  | in:  strR + strNSM, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strR + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strR + strAL + strNSM, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strR + strEN + strNSM + strNSM, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strR + strAN, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strR + strES + strNSM, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strR + strES + strNSM), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strR + strCS + strNSM + strNSM, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strR + strCS + strNSM + strNSM), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strR + strET, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strR + strET), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strR + strON + strNSM, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strR + strON + strNSM), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strR + strBN + strNSM + strNSM, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strR + strBN + strNSM + strNSM), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strR + strL + strNSM, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strR), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strR + strB + strNSM + strNSM, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strR), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strR + strS, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strR), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strR + strWS, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strR), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strAL + strNSM, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strAL + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strAL + strAL + strNSM, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strAL + strEN + strNSM + strNSM, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strAL + strAN, | 
|  | dir: bidi.RightToLeft, | 
|  | }, { | 
|  | in:  strAL + strES + strNSM, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strAL + strES + strNSM), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strAL + strCS + strNSM + strNSM, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strAL + strCS + strNSM + strNSM), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strAL + strET, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strAL + strET), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strAL + strON + strNSM, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strAL + strON + strNSM), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strAL + strBN + strNSM + strNSM, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strAL + strBN + strNSM + strNSM), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strAL + strL + strNSM, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strAL), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strAL + strB + strNSM + strNSM, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strAL), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strAL + strS, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strAL), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strAL + strWS, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strAL), | 
|  | err: ErrInvalid, | 
|  | }}, | 
|  |  | 
|  | // Rule 2.4: In an RTL label, if an EN is present, no AN may be present, | 
|  | // and vice versa. | 
|  | 4: []ruleTest{{ | 
|  | in:  strR + strEN + strAN, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strR + strEN), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strR + strAN + strEN + strNSM, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strR + strAN), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strAL + strEN + strAN, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strAL + strEN), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strAL + strAN + strEN + strNSM, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strAL + strAN), | 
|  | err: ErrInvalid, | 
|  | }}, | 
|  |  | 
|  | // Rule 2.5: In an LTR label, only characters with the Bidi properties L, | 
|  | // EN, ES, CS, ET, ON, BN, or NSM are allowed. | 
|  | 5: []ruleTest{{ | 
|  | in:  strL + strL + strL, | 
|  | dir: bidi.LeftToRight, | 
|  | }, { | 
|  | in:  strL + strEN + strL, | 
|  | dir: bidi.LeftToRight, | 
|  | }, { | 
|  | in:  strL + strES + strL, | 
|  | dir: bidi.LeftToRight, | 
|  | }, { | 
|  | in:  strL + strCS + strL, | 
|  | dir: bidi.LeftToRight, | 
|  | }, { | 
|  | in:  strL + strET + strL, | 
|  | dir: bidi.LeftToRight, | 
|  | }, { | 
|  | in:  strL + strON + strL, | 
|  | dir: bidi.LeftToRight, | 
|  | }, { | 
|  | in:  strL + strBN + strL, | 
|  | dir: bidi.LeftToRight, | 
|  | }, { | 
|  | in:  strL + strNSM + strL, | 
|  | dir: bidi.LeftToRight, | 
|  | }, { | 
|  | in:  strL + strR + strL, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strL), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strL + strAL + strL, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strL), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strL + strAN + strL, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strL), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strL + strB + strL, | 
|  | dir: bidi.LeftToRight, | 
|  | n:   len(strL + strAN + strL), | 
|  | err: nil, | 
|  | }, { | 
|  | in:  strL + strB + strL + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strL + strB + strL), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strL + strS + strL, | 
|  | dir: bidi.LeftToRight, | 
|  | n:   len(strL + strS + strL), | 
|  | err: nil, | 
|  | }, { | 
|  | in:  strL + strS + strL + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strL + strS + strL), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strL + strWS + strL, | 
|  | dir: bidi.LeftToRight, | 
|  | n:   len(strL + strWS + strL), | 
|  | err: nil, | 
|  | }, { | 
|  | in:  strL + strWS + strL + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strL + strWS + strL), | 
|  | err: ErrInvalid, | 
|  | }}, | 
|  |  | 
|  | // Rule 2.6: In an LTR label, the end of the label must be a character with | 
|  | // Bidi property L or EN, followed by zero or more characters with Bidi | 
|  | // property NSM. | 
|  | 6: []ruleTest{{ | 
|  | in:  strL, | 
|  | dir: bidi.LeftToRight, | 
|  | }, { | 
|  | in:  strL + strNSM, | 
|  | dir: bidi.LeftToRight, | 
|  | }, { | 
|  | in:  strL + strNSM + strNSM, | 
|  | dir: bidi.LeftToRight, | 
|  | }, { | 
|  | in:  strL + strEN, | 
|  | dir: bidi.LeftToRight, | 
|  | }, { | 
|  | in:  strL + strEN + strNSM, | 
|  | dir: bidi.LeftToRight, | 
|  | }, { | 
|  | in:  strL + strEN + strNSM + strNSM, | 
|  | dir: bidi.LeftToRight, | 
|  | }, { | 
|  | in:  strL + strES, | 
|  | dir: bidi.LeftToRight, | 
|  | n:   len(strL + strES), | 
|  | err: nil, | 
|  | }, { | 
|  | in:  strL + strES + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strL + strES), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strL + strCS, | 
|  | dir: bidi.LeftToRight, | 
|  | n:   len(strL + strCS), | 
|  | err: nil, | 
|  | }, { | 
|  | in:  strL + strCS + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strL + strCS), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strL + strET, | 
|  | dir: bidi.LeftToRight, | 
|  | n:   len(strL + strET), | 
|  | err: nil, | 
|  | }, { | 
|  | in:  strL + strET + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strL + strET), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strL + strON, | 
|  | dir: bidi.LeftToRight, | 
|  | n:   len(strL + strON), | 
|  | err: nil, | 
|  | }, { | 
|  | in:  strL + strON + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strL + strON), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strL + strBN, | 
|  | dir: bidi.LeftToRight, | 
|  | n:   len(strL + strBN), | 
|  | err: nil, | 
|  | }, { | 
|  | in:  strL + strBN + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strL + strBN), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strL + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strL), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strL + strAL, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strL), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strL + strAN, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strL), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strL + strB, | 
|  | dir: bidi.LeftToRight, | 
|  | n:   len(strL + strB), | 
|  | err: nil, | 
|  | }, { | 
|  | in:  strL + strB + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strL + strB), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strL + strB, | 
|  | dir: bidi.LeftToRight, | 
|  | n:   len(strL + strB), | 
|  | err: nil, | 
|  | }, { | 
|  | in:  strL + strB + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strL + strB), | 
|  | err: ErrInvalid, | 
|  | }, { | 
|  | in:  strL + strB, | 
|  | dir: bidi.LeftToRight, | 
|  | n:   len(strL + strB), | 
|  | err: nil, | 
|  | }, { | 
|  | in:  strL + strB + strR, | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strL + strB), | 
|  | err: ErrInvalid, | 
|  | }}, | 
|  |  | 
|  | // Incremental processing. | 
|  | 9: []ruleTest{{ | 
|  | in:  "e\u0301", // é | 
|  | dir: bidi.LeftToRight, | 
|  |  | 
|  | pSrc: 2, | 
|  | nSrc: 1, | 
|  | err0: transform.ErrShortSrc, | 
|  | }, { | 
|  | in:  "e\u1000f", // é | 
|  | dir: bidi.LeftToRight, | 
|  |  | 
|  | pSrc: 3, | 
|  | nSrc: 1, | 
|  | err0: transform.ErrShortSrc, | 
|  | }, { | 
|  | // Remain invalid once invalid. | 
|  | in:  strR + "ab", | 
|  | dir: bidi.RightToLeft, | 
|  | n:   len(strR), | 
|  | err: ErrInvalid, | 
|  |  | 
|  | pSrc: len(strR) + 1, | 
|  | nSrc: len(strR), | 
|  | err0: ErrInvalid, | 
|  | }, { | 
|  | // Short destination | 
|  | in:  "abcdefghij", | 
|  | dir: bidi.LeftToRight, | 
|  |  | 
|  | pSrc:  10, | 
|  | szDst: 5, | 
|  | nSrc:  5, | 
|  | err0:  transform.ErrShortDst, | 
|  | }, { | 
|  | // Short destination splitting input rune | 
|  | in:  "e\u0301", | 
|  | dir: bidi.LeftToRight, | 
|  |  | 
|  | pSrc:  3, | 
|  | szDst: 2, | 
|  | nSrc:  1, | 
|  | err0:  transform.ErrShortDst, | 
|  | }}, | 
|  | } | 
|  |  | 
|  | func init() { | 
|  | for rule, cases := range testCases { | 
|  | for i, tc := range cases { | 
|  | if tc.err == nil { | 
|  | testCases[rule][i].n = len(tc.in) | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | func doTests(t *testing.T, fn func(t *testing.T, tc ruleTest)) { | 
|  | for rule, cases := range testCases { | 
|  | for i, tc := range cases { | 
|  | name := fmt.Sprintf("%d/%d:%+q:%s", rule, i, tc.in, tc.in) | 
|  | testtext.Run(t, name, func(t *testing.T) { | 
|  | fn(t, tc) | 
|  | }) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestDirection(t *testing.T) { | 
|  | doTests(t, func(t *testing.T, tc ruleTest) { | 
|  | dir := Direction([]byte(tc.in)) | 
|  | if dir != tc.dir { | 
|  | t.Errorf("dir was %v; want %v", dir, tc.dir) | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestDirectionString(t *testing.T) { | 
|  | doTests(t, func(t *testing.T, tc ruleTest) { | 
|  | dir := DirectionString(tc.in) | 
|  | if dir != tc.dir { | 
|  | t.Errorf("dir was %v; want %v", dir, tc.dir) | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestValid(t *testing.T) { | 
|  | doTests(t, func(t *testing.T, tc ruleTest) { | 
|  | got := Valid([]byte(tc.in)) | 
|  | want := tc.err == nil | 
|  | if got != want { | 
|  | t.Fatalf("Valid: got %v; want %v", got, want) | 
|  | } | 
|  |  | 
|  | got = ValidString(tc.in) | 
|  | want = tc.err == nil | 
|  | if got != want { | 
|  | t.Fatalf("Valid: got %v; want %v", got, want) | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestSpan(t *testing.T) { | 
|  | doTests(t, func(t *testing.T, tc ruleTest) { | 
|  | // Skip tests that test for limited destination buffer size. | 
|  | if tc.szDst > 0 { | 
|  | return | 
|  | } | 
|  |  | 
|  | r := New() | 
|  | src := []byte(tc.in) | 
|  |  | 
|  | n, err := r.Span(src[:tc.pSrc], tc.pSrc == len(tc.in)) | 
|  | if err != tc.err0 { | 
|  | t.Errorf("err0 was %v; want %v", err, tc.err0) | 
|  | } | 
|  | if n != tc.nSrc { | 
|  | t.Fatalf("nSrc was %d; want %d", n, tc.nSrc) | 
|  | } | 
|  |  | 
|  | n, err = r.Span(src[n:], true) | 
|  | if err != tc.err { | 
|  | t.Errorf("error was %v; want %v", err, tc.err) | 
|  | } | 
|  | if got := n + tc.nSrc; got != tc.n { | 
|  | t.Errorf("n was %d; want %d", got, tc.n) | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestTransform(t *testing.T) { | 
|  | doTests(t, func(t *testing.T, tc ruleTest) { | 
|  | r := New() | 
|  |  | 
|  | src := []byte(tc.in) | 
|  | dst := make([]byte, len(tc.in)) | 
|  | if tc.szDst > 0 { | 
|  | dst = make([]byte, tc.szDst) | 
|  | } | 
|  |  | 
|  | // First transform operates on a zero-length string for most tests. | 
|  | nDst, nSrc, err := r.Transform(dst, src[:tc.pSrc], tc.pSrc == len(tc.in)) | 
|  | if err != tc.err0 { | 
|  | t.Errorf("err0 was %v; want %v", err, tc.err0) | 
|  | } | 
|  | if nDst != nSrc { | 
|  | t.Fatalf("nDst (%d) and nSrc (%d) should match", nDst, nSrc) | 
|  | } | 
|  | if nSrc != tc.nSrc { | 
|  | t.Fatalf("nSrc was %d; want %d", nSrc, tc.nSrc) | 
|  | } | 
|  |  | 
|  | dst1 := make([]byte, len(tc.in)) | 
|  | copy(dst1, dst[:nDst]) | 
|  |  | 
|  | nDst, nSrc, err = r.Transform(dst1[nDst:], src[nSrc:], true) | 
|  | if err != tc.err { | 
|  | t.Errorf("error was %v; want %v", err, tc.err) | 
|  | } | 
|  | if nDst != nSrc { | 
|  | t.Fatalf("nDst (%d) and nSrc (%d) should match", nDst, nSrc) | 
|  | } | 
|  | n := nSrc + tc.nSrc | 
|  | if n != tc.n { | 
|  | t.Fatalf("n was %d; want %d", n, tc.n) | 
|  | } | 
|  | if got, want := string(dst1[:n]), tc.in[:tc.n]; got != want { | 
|  | t.Errorf("got %+q; want %+q", got, want) | 
|  | } | 
|  | }) | 
|  | } |