diff options
Diffstat (limited to 'src/pkg/strings')
-rw-r--r-- | src/pkg/strings/example_test.go | 32 | ||||
-rw-r--r-- | src/pkg/strings/reader.go | 52 | ||||
-rw-r--r-- | src/pkg/strings/reader_test.go | 56 | ||||
-rw-r--r-- | src/pkg/strings/replace.go | 2 | ||||
-rw-r--r-- | src/pkg/strings/strings_test.go | 30 |
5 files changed, 138 insertions, 34 deletions
diff --git a/src/pkg/strings/example_test.go b/src/pkg/strings/example_test.go index 36e0a42fb..7243e16b1 100644 --- a/src/pkg/strings/example_test.go +++ b/src/pkg/strings/example_test.go @@ -7,6 +7,7 @@ package strings_test import ( "fmt" "strings" + "unicode" ) func ExampleFields() { @@ -14,6 +15,14 @@ func ExampleFields() { // Output: Fields are: ["foo" "bar" "baz"] } +func ExampleFieldsFunc() { + f := func(c rune) bool { + return !unicode.IsLetter(c) && !unicode.IsNumber(c) + } + fmt.Printf("Fields are: %q", strings.FieldsFunc(" foo1;bar2,baz3...", f)) + // Output: Fields are: ["foo1" "bar2" "baz3"] +} + func ExampleContains() { fmt.Println(strings.Contains("seafood", "foo")) fmt.Println(strings.Contains("seafood", "bar")) @@ -59,6 +68,25 @@ func ExampleIndex() { // -1 } +func ExampleIndexFunc() { + f := func(c rune) bool { + return unicode.Is(unicode.Han, c) + } + fmt.Println(strings.IndexFunc("Hello, 世界", f)) + fmt.Println(strings.IndexFunc("Hello, world", f)) + // Output: + // 7 + // -1 +} + +func ExampleIndexAny() { + fmt.Println(strings.IndexAny("chicken", "aeiouy")) + fmt.Println(strings.IndexAny("crwth", "aeiouy")) + // Output: + // 2 + // -1 +} + func ExampleIndexRune() { fmt.Println(strings.IndexRune("chicken", 'k')) fmt.Println(strings.IndexRune("chicken", 'd')) @@ -141,8 +169,8 @@ func ExampleToTitle() { } func ExampleTrim() { - fmt.Printf("[%q]", strings.Trim(" !!! Achtung !!! ", "! ")) - // Output: ["Achtung"] + fmt.Printf("[%q]", strings.Trim(" !!! Achtung! Achtung! !!! ", "! ")) + // Output: ["Achtung! Achtung"] } func ExampleMap() { diff --git a/src/pkg/strings/reader.go b/src/pkg/strings/reader.go index 11240efc0..82df97439 100644 --- a/src/pkg/strings/reader.go +++ b/src/pkg/strings/reader.go @@ -15,40 +15,41 @@ import ( // from a string. type Reader struct { s string - i int // current reading index - prevRune int // index of previous rune; or < 0 + i int64 // current reading index + prevRune int // index of previous rune; or < 0 } // Len returns the number of bytes of the unread portion of the // string. func (r *Reader) Len() int { - if r.i >= len(r.s) { + if r.i >= int64(len(r.s)) { return 0 } - return len(r.s) - r.i + return int(int64(len(r.s)) - r.i) } func (r *Reader) Read(b []byte) (n int, err error) { if len(b) == 0 { return 0, nil } - if r.i >= len(r.s) { + if r.i >= int64(len(r.s)) { return 0, io.EOF } - n = copy(b, r.s[r.i:]) - r.i += n r.prevRune = -1 + n = copy(b, r.s[r.i:]) + r.i += int64(n) return } func (r *Reader) ReadAt(b []byte, off int64) (n int, err error) { + // cannot modify state - see io.ReaderAt if off < 0 { - return 0, errors.New("strings: invalid offset") + return 0, errors.New("strings.Reader.ReadAt: negative offset") } if off >= int64(len(r.s)) { return 0, io.EOF } - n = copy(b, r.s[int(off):]) + n = copy(b, r.s[off:]) if n < len(b) { err = io.EOF } @@ -56,49 +57,51 @@ func (r *Reader) ReadAt(b []byte, off int64) (n int, err error) { } func (r *Reader) ReadByte() (b byte, err error) { - if r.i >= len(r.s) { + r.prevRune = -1 + if r.i >= int64(len(r.s)) { return 0, io.EOF } b = r.s[r.i] r.i++ - r.prevRune = -1 return } func (r *Reader) UnreadByte() error { + r.prevRune = -1 if r.i <= 0 { - return errors.New("strings.Reader: at beginning of string") + return errors.New("strings.Reader.UnreadByte: at beginning of string") } r.i-- - r.prevRune = -1 return nil } func (r *Reader) ReadRune() (ch rune, size int, err error) { - if r.i >= len(r.s) { + if r.i >= int64(len(r.s)) { + r.prevRune = -1 return 0, 0, io.EOF } - r.prevRune = r.i + r.prevRune = int(r.i) if c := r.s[r.i]; c < utf8.RuneSelf { r.i++ return rune(c), 1, nil } ch, size = utf8.DecodeRuneInString(r.s[r.i:]) - r.i += size + r.i += int64(size) return } func (r *Reader) UnreadRune() error { if r.prevRune < 0 { - return errors.New("strings.Reader: previous operation was not ReadRune") + return errors.New("strings.Reader.UnreadRune: previous operation was not ReadRune") } - r.i = r.prevRune + r.i = int64(r.prevRune) r.prevRune = -1 return nil } // Seek implements the io.Seeker interface. func (r *Reader) Seek(offset int64, whence int) (int64, error) { + r.prevRune = -1 var abs int64 switch whence { case 0: @@ -108,22 +111,19 @@ func (r *Reader) Seek(offset int64, whence int) (int64, error) { case 2: abs = int64(len(r.s)) + offset default: - return 0, errors.New("strings: invalid whence") + return 0, errors.New("strings.Reader.Seek: invalid whence") } if abs < 0 { - return 0, errors.New("strings: negative position") - } - if abs >= 1<<31 { - return 0, errors.New("strings: position out of range") + return 0, errors.New("strings.Reader.Seek: negative position") } - r.i = int(abs) + r.i = abs return abs, nil } // WriteTo implements the io.WriterTo interface. func (r *Reader) WriteTo(w io.Writer) (n int64, err error) { r.prevRune = -1 - if r.i >= len(r.s) { + if r.i >= int64(len(r.s)) { return 0, nil } s := r.s[r.i:] @@ -131,7 +131,7 @@ func (r *Reader) WriteTo(w io.Writer) (n int64, err error) { if m > len(s) { panic("strings.Reader.WriteTo: invalid WriteString count") } - r.i += m + r.i += int64(m) n = int64(m) if m != len(s) && err == nil { err = io.ErrShortWrite diff --git a/src/pkg/strings/reader_test.go b/src/pkg/strings/reader_test.go index 4fdddcdb5..bee90eb25 100644 --- a/src/pkg/strings/reader_test.go +++ b/src/pkg/strings/reader_test.go @@ -10,6 +10,7 @@ import ( "io" "os" "strings" + "sync" "testing" ) @@ -26,9 +27,9 @@ func TestReader(t *testing.T) { {seek: os.SEEK_SET, off: 0, n: 20, want: "0123456789"}, {seek: os.SEEK_SET, off: 1, n: 1, want: "1"}, {seek: os.SEEK_CUR, off: 1, wantpos: 3, n: 2, want: "34"}, - {seek: os.SEEK_SET, off: -1, seekerr: "strings: negative position"}, - {seek: os.SEEK_SET, off: 1<<31 - 1}, - {seek: os.SEEK_CUR, off: 1, seekerr: "strings: position out of range"}, + {seek: os.SEEK_SET, off: -1, seekerr: "strings.Reader.Seek: negative position"}, + {seek: os.SEEK_SET, off: 1 << 33, wantpos: 1 << 33}, + {seek: os.SEEK_CUR, off: 1, wantpos: 1<<33 + 1}, {seek: os.SEEK_SET, n: 5, want: "01234"}, {seek: os.SEEK_CUR, n: 5, want: "56789"}, {seek: os.SEEK_END, off: -1, n: 1, wantpos: 9, want: "9"}, @@ -60,6 +61,16 @@ func TestReader(t *testing.T) { } } +func TestReadAfterBigSeek(t *testing.T) { + r := strings.NewReader("0123456789") + if _, err := r.Seek(1<<31+5, os.SEEK_SET); err != nil { + t.Fatal(err) + } + if n, err := r.Read(make([]byte, 10)); n != 0 || err != io.EOF { + t.Errorf("Read = %d, %v; want 0, EOF", n, err) + } +} + func TestReaderAt(t *testing.T) { r := strings.NewReader("0123456789") tests := []struct { @@ -73,7 +84,7 @@ func TestReaderAt(t *testing.T) { {1, 9, "123456789", nil}, {11, 10, "", io.EOF}, {0, 0, "", nil}, - {-1, 0, "", "strings: invalid offset"}, + {-1, 0, "", "strings.Reader.ReadAt: negative offset"}, } for i, tt := range tests { b := make([]byte, tt.n) @@ -88,6 +99,43 @@ func TestReaderAt(t *testing.T) { } } +func TestReaderAtConcurrent(t *testing.T) { + // Test for the race detector, to verify ReadAt doesn't mutate + // any state. + r := strings.NewReader("0123456789") + var wg sync.WaitGroup + for i := 0; i < 5; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + var buf [1]byte + r.ReadAt(buf[:], int64(i)) + }(i) + } + wg.Wait() +} + +func TestEmptyReaderConcurrent(t *testing.T) { + // Test for the race detector, to verify a Read that doesn't yield any bytes + // is okay to use from multiple goroutines. This was our historic behavior. + // See golang.org/issue/7856 + r := strings.NewReader("") + var wg sync.WaitGroup + for i := 0; i < 5; i++ { + wg.Add(2) + go func() { + defer wg.Done() + var buf [1]byte + r.Read(buf[:]) + }() + go func() { + defer wg.Done() + r.Read(nil) + }() + } + wg.Wait() +} + func TestWriteTo(t *testing.T) { const str = "0123456789" for i := 0; i <= len(str); i++ { diff --git a/src/pkg/strings/replace.go b/src/pkg/strings/replace.go index 54c9323e0..3e05d2057 100644 --- a/src/pkg/strings/replace.go +++ b/src/pkg/strings/replace.go @@ -492,7 +492,7 @@ func (r *byteStringReplacer) Replace(s string) string { for i := 0; i < len(s); i++ { b := s[i] if r.old[b>>5]&uint32(1<<(b&31)) != 0 { - n := copy(bi[:], r.new[b]) + n := copy(bi, r.new[b]) bi = bi[n:] } else { bi[0] = b diff --git a/src/pkg/strings/strings_test.go b/src/pkg/strings/strings_test.go index df0dd7165..e40a18015 100644 --- a/src/pkg/strings/strings_test.go +++ b/src/pkg/strings/strings_test.go @@ -652,7 +652,7 @@ func equal(m string, s1, s2 string, t *testing.T) bool { e1 := Split(s1, "") e2 := Split(s2, "") for i, c1 := range e1 { - if i > len(e2) { + if i >= len(e2) { break } r1, _ := utf8.DecodeRuneInString(c1) @@ -858,6 +858,32 @@ func TestReadRune(t *testing.T) { } } +var UnreadRuneErrorTests = []struct { + name string + f func(*Reader) +}{ + {"Read", func(r *Reader) { r.Read([]byte{0}) }}, + {"ReadByte", func(r *Reader) { r.ReadByte() }}, + {"UnreadRune", func(r *Reader) { r.UnreadRune() }}, + {"Seek", func(r *Reader) { r.Seek(0, 1) }}, + {"WriteTo", func(r *Reader) { r.WriteTo(&bytes.Buffer{}) }}, +} + +func TestUnreadRuneError(t *testing.T) { + for _, tt := range UnreadRuneErrorTests { + reader := NewReader("0123456789") + if _, _, err := reader.ReadRune(); err != nil { + // should not happen + t.Fatal(err) + } + tt.f(reader) + err := reader.UnreadRune() + if err == nil { + t.Errorf("Unreading after %s: expected error", tt.name) + } + } +} + var ReplaceTests = []struct { in string old, new string @@ -903,6 +929,8 @@ var TitleTests = []struct { {"123a456", "123a456"}, {"double-blind", "Double-Blind"}, {"ÿøû", "Ÿøû"}, + {"with_underscore", "With_underscore"}, + {"unicode \xe2\x80\xa8 line separator", "Unicode \xe2\x80\xa8 Line Separator"}, } func TestTitle(t *testing.T) { |