diff options
author | Ian Lance Taylor <iant@golang.org> | 2017-01-14 00:05:42 +0000 |
---|---|---|
committer | Ian Lance Taylor <ian@gcc.gnu.org> | 2017-01-14 00:05:42 +0000 |
commit | c2047754c300b68c05d65faa8dc2925fe67b71b4 (patch) | |
tree | e183ae81a1f48a02945cb6de463a70c5be1b06f6 /libgo/go/mime | |
parent | 829afb8f05602bb31c9c597b24df7377fed4f059 (diff) | |
download | gcc-c2047754c300b68c05d65faa8dc2925fe67b71b4.zip gcc-c2047754c300b68c05d65faa8dc2925fe67b71b4.tar.gz gcc-c2047754c300b68c05d65faa8dc2925fe67b71b4.tar.bz2 |
libgo: update to Go 1.8 release candidate 1
Compiler changes:
* Change map assignment to use mapassign and assign value directly.
* Change string iteration to use decoderune, faster for ASCII strings.
* Change makeslice to take int, and use makeslice64 for larger values.
* Add new noverflow field to hmap struct used for maps.
Unresolved problems, to be fixed later:
* Commented out test in go/types/sizes_test.go that doesn't compile.
* Commented out reflect.TestStructOf test for padding after zero-sized field.
Reviewed-on: https://go-review.googlesource.com/35231
gotools/:
Updates for Go 1.8rc1.
* Makefile.am (go_cmd_go_files): Add bug.go.
(s-zdefaultcc): Write defaultPkgConfig.
* Makefile.in: Rebuild.
From-SVN: r244456
Diffstat (limited to 'libgo/go/mime')
-rw-r--r-- | libgo/go/mime/mediatype.go | 37 | ||||
-rw-r--r-- | libgo/go/mime/mediatype_test.go | 14 | ||||
-rw-r--r-- | libgo/go/mime/multipart/formdata.go | 4 | ||||
-rw-r--r-- | libgo/go/mime/multipart/multipart.go | 219 | ||||
-rw-r--r-- | libgo/go/mime/multipart/multipart_test.go | 69 | ||||
-rw-r--r-- | libgo/go/mime/quotedprintable/example_test.go | 39 | ||||
-rw-r--r-- | libgo/go/mime/quotedprintable/reader.go | 13 | ||||
-rw-r--r-- | libgo/go/mime/quotedprintable/reader_test.go | 16 |
8 files changed, 271 insertions, 140 deletions
diff --git a/libgo/go/mime/mediatype.go b/libgo/go/mime/mediatype.go index 1845401..75cc903 100644 --- a/libgo/go/mime/mediatype.go +++ b/libgo/go/mime/mediatype.go @@ -248,24 +248,33 @@ func consumeValue(v string) (value, rest string) { } // parse a quoted-string - rest = v[1:] // consume the leading quote buffer := new(bytes.Buffer) - var nextIsLiteral bool - for idx, r := range rest { - switch { - case nextIsLiteral: - buffer.WriteRune(r) - nextIsLiteral = false - case r == '"': - return buffer.String(), rest[idx+1:] - case r == '\\': - nextIsLiteral = true - case r != '\r' && r != '\n': - buffer.WriteRune(r) - default: + for i := 1; i < len(v); i++ { + r := v[i] + if r == '"' { + return buffer.String(), v[i+1:] + } + // When MSIE sends a full file path (in "intranet mode"), it does not + // escape backslashes: "C:\dev\go\foo.txt", not "C:\\dev\\go\\foo.txt". + // + // No known MIME generators emit unnecessary backslash escapes + // for simple token characters like numbers and letters. + // + // If we see an unnecessary backslash escape, assume it is from MSIE + // and intended as a literal backslash. This makes Go servers deal better + // with MSIE without affecting the way they handle conforming MIME + // generators. + if r == '\\' && i+1 < len(v) && !isTokenChar(rune(v[i+1])) { + buffer.WriteByte(v[i+1]) + i++ + continue + } + if r == '\r' || r == '\n' { return "", v } + buffer.WriteByte(v[i]) } + // Did not find end quote. return "", v } diff --git a/libgo/go/mime/mediatype_test.go b/libgo/go/mime/mediatype_test.go index 9afa558..c5fc906 100644 --- a/libgo/go/mime/mediatype_test.go +++ b/libgo/go/mime/mediatype_test.go @@ -138,10 +138,11 @@ func TestParseMediaType(t *testing.T) { m("title", "This is even more ***fun*** isn't it!")}, // Tests from http://greenbytes.de/tech/tc2231/ + // Note: Backslash escape handling is a bit loose, like MSIE. // TODO(bradfitz): add the rest of the tests from that site. {`attachment; filename="f\oo.html"`, "attachment", - m("filename", "foo.html")}, + m("filename", "f\\oo.html")}, {`attachment; filename="\"quoting\" tested.html"`, "attachment", m("filename", `"quoting" tested.html`)}, @@ -165,7 +166,7 @@ func TestParseMediaType(t *testing.T) { m("filename", "foo-%41.html")}, {`attachment; filename="foo-%\41.html"`, "attachment", - m("filename", "foo-%41.html")}, + m("filename", "foo-%\\41.html")}, {`filename=foo.html`, "", m()}, {`x=y; filename=foo.html`, @@ -220,18 +221,21 @@ func TestParseMediaType(t *testing.T) { // Empty string used to be mishandled. {`foo; bar=""`, "foo", m("bar", "")}, + + // Microsoft browers in intranet mode do not think they need to escape \ in file name. + {`form-data; name="file"; filename="C:\dev\go\robots.txt"`, "form-data", m("name", "file", "filename", `C:\dev\go\robots.txt`)}, } for _, test := range tests { mt, params, err := ParseMediaType(test.in) if err != nil { if test.t != "" { - t.Errorf("for input %q, unexpected error: %v", test.in, err) + t.Errorf("for input %#q, unexpected error: %v", test.in, err) continue } continue } if g, e := mt, test.t; g != e { - t.Errorf("for input %q, expected type %q, got %q", + t.Errorf("for input %#q, expected type %q, got %q", test.in, e, g) continue } @@ -239,7 +243,7 @@ func TestParseMediaType(t *testing.T) { continue } if !reflect.DeepEqual(params, test.p) { - t.Errorf("for input %q, wrong params.\n"+ + t.Errorf("for input %#q, wrong params.\n"+ "expected: %#v\n"+ " got: %#v", test.in, test.p, params) diff --git a/libgo/go/mime/multipart/formdata.go b/libgo/go/mime/multipart/formdata.go index 8085bd3..c9e3188 100644 --- a/libgo/go/mime/multipart/formdata.go +++ b/libgo/go/mime/multipart/formdata.go @@ -79,8 +79,10 @@ func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) { if err != nil { return nil, err } - defer file.Close() _, err = io.Copy(file, io.MultiReader(&b, p)) + if cerr := file.Close(); err == nil { + err = cerr + } if err != nil { os.Remove(file.Name()) return nil, err diff --git a/libgo/go/mime/multipart/multipart.go b/libgo/go/mime/multipart/multipart.go index 205348c..1954808 100644 --- a/libgo/go/mime/multipart/multipart.go +++ b/libgo/go/mime/multipart/multipart.go @@ -42,9 +42,7 @@ type Part struct { // during Read calls. Header textproto.MIMEHeader - buffer *bytes.Buffer - mr *Reader - bytesRead int + mr *Reader disposition string dispositionParams map[string]string @@ -53,6 +51,11 @@ type Part struct { // wrapper around such a reader, decoding the // Content-Transfer-Encoding r io.Reader + + n int // known data bytes waiting in mr.bufReader + total int64 // total data bytes read already + err error // error to return when n == 0 + readErr error // read error observed from mr.bufReader } // FormName returns the name parameter if p has a Content-Disposition @@ -126,7 +129,6 @@ func newPart(mr *Reader) (*Part, error) { bp := &Part{ Header: make(map[string][]string), mr: mr, - buffer: new(bytes.Buffer), } if err := bp.populateHeaders(); err != nil { return nil, err @@ -161,65 +163,118 @@ type partReader struct { p *Part } -func (pr partReader) Read(d []byte) (n int, err error) { +func (pr partReader) Read(d []byte) (int, error) { p := pr.p - defer func() { - p.bytesRead += n - }() - if p.buffer.Len() >= len(d) { - // Internal buffer of unconsumed data is large enough for - // the read request. No need to parse more at the moment. - return p.buffer.Read(d) + br := p.mr.bufReader + + // Read into buffer until we identify some data to return, + // or we find a reason to stop (boundary or read error). + for p.n == 0 && p.err == nil { + peek, _ := br.Peek(br.Buffered()) + p.n, p.err = scanUntilBoundary(peek, p.mr.dashBoundary, p.mr.nlDashBoundary, p.total, p.readErr) + if p.n == 0 && p.err == nil { + // Force buffered I/O to read more into buffer. + _, p.readErr = br.Peek(len(peek) + 1) + if p.readErr == io.EOF { + p.readErr = io.ErrUnexpectedEOF + } + } } - peek, err := p.mr.bufReader.Peek(peekBufferSize) // TODO(bradfitz): add buffer size accessor - - // Look for an immediate empty part without a leading \r\n - // before the boundary separator. Some MIME code makes empty - // parts like this. Most browsers, however, write the \r\n - // before the subsequent boundary even for empty parts and - // won't hit this path. - if p.bytesRead == 0 && p.mr.peekBufferIsEmptyPart(peek) { - return 0, io.EOF + + // Read out from "data to return" part of buffer. + if p.n == 0 { + return 0, p.err } - unexpectedEOF := err == io.EOF - if err != nil && !unexpectedEOF { - return 0, fmt.Errorf("multipart: Part Read: %v", err) + n := len(d) + if n > p.n { + n = p.n } - if peek == nil { - panic("nil peek buf") + n, _ = br.Read(d[:n]) + p.total += int64(n) + p.n -= n + if p.n == 0 { + return n, p.err } - // Search the peek buffer for "\r\n--boundary". If found, - // consume everything up to the boundary. If not, consume only - // as much of the peek buffer as cannot hold the boundary - // string. - nCopy := 0 - foundBoundary := false - if idx, isEnd := p.mr.peekBufferSeparatorIndex(peek); idx != -1 { - nCopy = idx - foundBoundary = isEnd - if !isEnd && nCopy == 0 { - nCopy = 1 // make some progress. + return n, nil +} + +// scanUntilBoundary scans buf to identify how much of it can be safely +// returned as part of the Part body. +// dashBoundary is "--boundary". +// nlDashBoundary is "\r\n--boundary" or "\n--boundary", depending on what mode we are in. +// The comments below (and the name) assume "\n--boundary", but either is accepted. +// total is the number of bytes read out so far. If total == 0, then a leading "--boundary" is recognized. +// readErr is the read error, if any, that followed reading the bytes in buf. +// scanUntilBoundary returns the number of data bytes from buf that can be +// returned as part of the Part body and also the error to return (if any) +// once those data bytes are done. +func scanUntilBoundary(buf, dashBoundary, nlDashBoundary []byte, total int64, readErr error) (int, error) { + if total == 0 { + // At beginning of body, allow dashBoundary. + if bytes.HasPrefix(buf, dashBoundary) { + switch matchAfterPrefix(buf, dashBoundary, readErr) { + case -1: + return len(dashBoundary), nil + case 0: + return 0, nil + case +1: + return 0, io.EOF + } + } + if bytes.HasPrefix(dashBoundary, buf) { + return 0, readErr } - } else if safeCount := len(peek) - len(p.mr.nlDashBoundary); safeCount > 0 { - nCopy = safeCount - } else if unexpectedEOF { - // If we've run out of peek buffer and the boundary - // wasn't found (and can't possibly fit), we must have - // hit the end of the file unexpectedly. - return 0, io.ErrUnexpectedEOF } - if nCopy > 0 { - if _, err := io.CopyN(p.buffer, p.mr.bufReader, int64(nCopy)); err != nil { - return 0, err + + // Search for "\n--boundary". + if i := bytes.Index(buf, nlDashBoundary); i >= 0 { + switch matchAfterPrefix(buf[i:], nlDashBoundary, readErr) { + case -1: + return i + len(nlDashBoundary), nil + case 0: + return i, nil + case +1: + return i, io.EOF + } + } + if bytes.HasPrefix(nlDashBoundary, buf) { + return 0, readErr + } + + // Otherwise, anything up to the final \n is not part of the boundary + // and so must be part of the body. + // Also if the section from the final \n onward is not a prefix of the boundary, + // it too must be part of the body. + i := bytes.LastIndexByte(buf, nlDashBoundary[0]) + if i >= 0 && bytes.HasPrefix(nlDashBoundary, buf[i:]) { + return i, nil + } + return len(buf), readErr +} + +// matchAfterPrefix checks whether buf should be considered to match the boundary. +// The prefix is "--boundary" or "\r\n--boundary" or "\n--boundary", +// and the caller has verified already that bytes.HasPrefix(buf, prefix) is true. +// +// matchAfterPrefix returns +1 if the buffer does match the boundary, +// meaning the prefix is followed by a dash, space, tab, cr, nl, or end of input. +// It returns -1 if the buffer definitely does NOT match the boundary, +// meaning the prefix is followed by some other character. +// For example, "--foobar" does not match "--foo". +// It returns 0 more input needs to be read to make the decision, +// meaning that len(buf) == len(prefix) and readErr == nil. +func matchAfterPrefix(buf, prefix []byte, readErr error) int { + if len(buf) == len(prefix) { + if readErr != nil { + return +1 } + return 0 } - n, err = p.buffer.Read(d) - if err == io.EOF && !foundBoundary { - // If the boundary hasn't been reached there's more to - // read, so don't pass through an EOF from the buffer - err = nil + c := buf[len(prefix)] + if c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '-' { + return +1 } - return + return -1 } func (p *Part) Close() error { @@ -337,64 +392,6 @@ func (mr *Reader) isBoundaryDelimiterLine(line []byte) (ret bool) { return bytes.Equal(rest, mr.nl) } -// peekBufferIsEmptyPart reports whether the provided peek-ahead -// buffer represents an empty part. It is called only if we've not -// already read any bytes in this part and checks for the case of MIME -// software not writing the \r\n on empty parts. Some does, some -// doesn't. -// -// This checks that what follows the "--boundary" is actually the end -// ("--boundary--" with optional whitespace) or optional whitespace -// and then a newline, so we don't catch "--boundaryFAKE", in which -// case the whole line is part of the data. -func (mr *Reader) peekBufferIsEmptyPart(peek []byte) bool { - // End of parts case. - // Test whether peek matches `^--boundary--[ \t]*(?:\r\n|$)` - if bytes.HasPrefix(peek, mr.dashBoundaryDash) { - rest := peek[len(mr.dashBoundaryDash):] - rest = skipLWSPChar(rest) - return bytes.HasPrefix(rest, mr.nl) || len(rest) == 0 - } - if !bytes.HasPrefix(peek, mr.dashBoundary) { - return false - } - // Test whether rest matches `^[ \t]*\r\n`) - rest := peek[len(mr.dashBoundary):] - rest = skipLWSPChar(rest) - return bytes.HasPrefix(rest, mr.nl) -} - -// peekBufferSeparatorIndex returns the index of mr.nlDashBoundary in -// peek and whether it is a real boundary (and not a prefix of an -// unrelated separator). To be the end, the peek buffer must contain a -// newline after the boundary or contain the ending boundary (--separator--). -func (mr *Reader) peekBufferSeparatorIndex(peek []byte) (idx int, isEnd bool) { - idx = bytes.Index(peek, mr.nlDashBoundary) - if idx == -1 { - return - } - - peek = peek[idx+len(mr.nlDashBoundary):] - if len(peek) == 0 || len(peek) == 1 && peek[0] == '-' { - return idx, false - } - if len(peek) > 1 && peek[0] == '-' && peek[1] == '-' { - return idx, true - } - peek = skipLWSPChar(peek) - // Don't have a complete line after the peek. - if bytes.IndexByte(peek, '\n') == -1 { - return idx, false - } - if len(peek) > 0 && peek[0] == '\n' { - return idx, true - } - if len(peek) > 1 && peek[0] == '\r' && peek[1] == '\n' { - return idx, true - } - return idx, false -} - // skipLWSPChar returns b with leading spaces and tabs removed. // RFC 822 defines: // LWSP-char = SPACE / HTAB diff --git a/libgo/go/mime/multipart/multipart_test.go b/libgo/go/mime/multipart/multipart_test.go index 82a7f86..7fbee90 100644 --- a/libgo/go/mime/multipart/multipart_test.go +++ b/libgo/go/mime/multipart/multipart_test.go @@ -125,6 +125,7 @@ func TestMultipartSlowInput(t *testing.T) { } func testMultipart(t *testing.T, r io.Reader, onlyNewlines bool) { + t.Parallel() reader := NewReader(r, "MyBoundary") buf := new(bytes.Buffer) @@ -323,6 +324,73 @@ func (s *slowReader) Read(p []byte) (int, error) { return s.r.Read(p[:1]) } +type sentinelReader struct { + // done is closed when this reader is read from. + done chan struct{} +} + +func (s *sentinelReader) Read([]byte) (int, error) { + if s.done != nil { + close(s.done) + s.done = nil + } + return 0, io.EOF +} + +// TestMultipartStreamReadahead tests that PartReader does not block +// on reading past the end of a part, ensuring that it can be used on +// a stream like multipart/x-mixed-replace. See golang.org/issue/15431 +func TestMultipartStreamReadahead(t *testing.T) { + testBody1 := ` +This is a multi-part message. This line is ignored. +--MyBoundary +foo-bar: baz + +Body +--MyBoundary +` + testBody2 := `foo-bar: bop + +Body 2 +--MyBoundary-- +` + done1 := make(chan struct{}) + reader := NewReader( + io.MultiReader( + strings.NewReader(testBody1), + &sentinelReader{done1}, + strings.NewReader(testBody2)), + "MyBoundary") + + var i int + readPart := func(hdr textproto.MIMEHeader, body string) { + part, err := reader.NextPart() + if part == nil || err != nil { + t.Fatalf("Part %d: NextPart failed: %v", i, err) + } + + if !reflect.DeepEqual(part.Header, hdr) { + t.Errorf("Part %d: part.Header = %v, want %v", i, part.Header, hdr) + } + data, err := ioutil.ReadAll(part) + expectEq(t, body, string(data), fmt.Sprintf("Part %d body", i)) + if err != nil { + t.Fatalf("Part %d: ReadAll failed: %v", i, err) + } + i++ + } + + readPart(textproto.MIMEHeader{"Foo-Bar": {"baz"}}, "Body") + + select { + case <-done1: + t.Errorf("Reader read past second boundary") + default: + } + + readPart(textproto.MIMEHeader{"Foo-Bar": {"bop"}}, "Body 2") +} + func TestLineContinuation(t *testing.T) { // This body, extracted from an email, contains headers that span multiple // lines. @@ -755,6 +823,7 @@ func partsFromReader(r *Reader) ([]headerBody, error) { } func TestParseAllSizes(t *testing.T) { + t.Parallel() const maxSize = 5 << 10 var buf bytes.Buffer body := strings.Repeat("a", maxSize) diff --git a/libgo/go/mime/quotedprintable/example_test.go b/libgo/go/mime/quotedprintable/example_test.go new file mode 100644 index 0000000..0593b04 --- /dev/null +++ b/libgo/go/mime/quotedprintable/example_test.go @@ -0,0 +1,39 @@ +// 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. + +// +build ignore + +package quotedprintable_test + +import ( + "fmt" + "io/ioutil" + "mime/quotedprintable" + "os" + "strings" +) + +func ExampleNewReader() { + for _, s := range []string{ + `=48=65=6C=6C=6F=2C=20=47=6F=70=68=65=72=73=21`, + `invalid escape: <b style="font-size: 200%">hello</b>`, + "Hello, Gophers! This symbol will be unescaped: =3D and this will be written in =\r\none line.", + } { + b, err := ioutil.ReadAll(quotedprintable.NewReader(strings.NewReader(s))) + fmt.Printf("%s %v\n", b, err) + } + // Output: + // Hello, Gophers! <nil> + // invalid escape: <b style="font-size: 200%">hello</b> <nil> + // Hello, Gophers! This symbol will be unescaped: = and this will be written in one line. <nil> +} + +func ExampleNewWriter() { + w := quotedprintable.NewWriter(os.Stdout) + w.Write([]byte("These symbols will be escaped: = \t")) + w.Close() + + // Output: + // These symbols will be escaped: =3D =09 +} diff --git a/libgo/go/mime/quotedprintable/reader.go b/libgo/go/mime/quotedprintable/reader.go index 3bd6833..b142240 100644 --- a/libgo/go/mime/quotedprintable/reader.go +++ b/libgo/go/mime/quotedprintable/reader.go @@ -74,6 +74,11 @@ func (r *Reader) Read(p []byte) (n int, err error) { // 1. in addition to "=\r\n", "=\n" is also treated as soft line break. // 2. it will pass through a '\r' or '\n' not preceded by '=', consistent // with other broken QP encoders & decoders. + // 3. it accepts soft line-break (=) at end of message (issue 15486); i.e. + // the final byte read from the underlying reader is allowed to be '=', + // and it will be silently ignored. + // 4. it takes = as literal = if not followed by two hex digits + // but not at end of line (issue 13219). for len(p) > 0 { if len(r.line) == 0 { if r.rerr != nil { @@ -89,7 +94,8 @@ func (r *Reader) Read(p []byte) (n int, err error) { if bytes.HasSuffix(r.line, softSuffix) { rightStripped := wholeLine[len(r.line):] r.line = r.line[:len(r.line)-1] - if !bytes.HasPrefix(rightStripped, lf) && !bytes.HasPrefix(rightStripped, crlf) { + if !bytes.HasPrefix(rightStripped, lf) && !bytes.HasPrefix(rightStripped, crlf) && + !(len(rightStripped) == 0 && len(r.line) > 0 && r.rerr == io.EOF) { r.rerr = fmt.Errorf("quotedprintable: invalid bytes after =: %q", rightStripped) } } else if hasLF { @@ -107,6 +113,11 @@ func (r *Reader) Read(p []byte) (n int, err error) { case b == '=': b, err = readHexByte(r.line[1:]) if err != nil { + if len(r.line) >= 2 && r.line[1] != '\r' && r.line[1] != '\n' { + // Take the = as a literal =. + b = '=' + break + } return n, err } r.line = r.line[2:] // 2 of the 3; other 1 is done below diff --git a/libgo/go/mime/quotedprintable/reader_test.go b/libgo/go/mime/quotedprintable/reader_test.go index e77b261..ca016f9 100644 --- a/libgo/go/mime/quotedprintable/reader_test.go +++ b/libgo/go/mime/quotedprintable/reader_test.go @@ -30,7 +30,7 @@ func TestReader(t *testing.T) { {in: "foo bar=3d", want: "foo bar="}, // lax. {in: "foo bar=\n", want: "foo bar"}, {in: "foo bar\n", want: "foo bar\n"}, // somewhat lax. - {in: "foo bar=0", want: "foo bar", err: io.ErrUnexpectedEOF}, + {in: "foo bar=0", want: "foo bar=0"}, // lax {in: "foo bar=0D=0A", want: "foo bar\r\n"}, {in: " A B \r\n C ", want: " A B\r\n C"}, {in: " A B =\r\n C ", want: " A B C"}, @@ -58,6 +58,9 @@ func TestReader(t *testing.T) { {in: "foo=\nbar", want: "foobar"}, {in: "foo=\rbar", want: "foo", err: "quotedprintable: invalid hex byte 0x0d"}, {in: "foo=\r\r\r \nbar", want: "foo", err: `quotedprintable: invalid bytes after =: "\r\r\r \n"`}, + // Issue 15486, accept trailing soft line-break at end of input. + {in: "foo=", want: "foo"}, + {in: "=", want: "", err: `quotedprintable: invalid bytes after =: ""`}, // Example from RFC 2045: {in: "Now's the time =\n" + "for all folk to come=\n" + " to the aid of their country.", @@ -191,13 +194,10 @@ func TestExhaustive(t *testing.T) { } sort.Strings(outcomes) got := strings.Join(outcomes, "\n") - want := `OK: 21576 -invalid bytes after =: 3397 -quotedprintable: invalid hex byte 0x0a: 1400 -quotedprintable: invalid hex byte 0x0d: 2700 -quotedprintable: invalid hex byte 0x20: 2490 -quotedprintable: invalid hex byte 0x3d: 440 -unexpected EOF: 3122` + want := `OK: 28934 +invalid bytes after =: 3949 +quotedprintable: invalid hex byte 0x0d: 2048 +unexpected EOF: 194` if got != want { t.Errorf("Got:\n%s\nWant:\n%s", got, want) } |