dns/dnsmessage: implement fmt.GoStringer.GoString

This improves the debugability of the library and eases the transition
to this library. For example, test DNS messages defined with another DNS
library can be packed and then unpacked with this library. These
unpacked messages can have GoString called on them to generate new test
messages defined with this library.

Updates golang/go#16218

Change-Id: I602586500fd8202892ef04187d3bd8a11039cf27
Reviewed-on: https://go-review.googlesource.com/120697
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/dns/dnsmessage/message.go b/dns/dnsmessage/message.go
index 38f8177..dc5b143 100644
--- a/dns/dnsmessage/message.go
+++ b/dns/dnsmessage/message.go
@@ -21,16 +21,6 @@
 // A Type is a type of DNS request and response.
 type Type uint16
 
-// A Class is a type of network.
-type Class uint16
-
-// An OpCode is a DNS operation code.
-type OpCode uint16
-
-// An RCode is a DNS response status code.
-type RCode uint16
-
-// Wire constants.
 const (
 	// ResourceHeader.Type and Question.Type
 	TypeA     Type = 1
@@ -50,7 +40,46 @@
 	TypeMINFO Type = 14
 	TypeAXFR  Type = 252
 	TypeALL   Type = 255
+)
 
+var typeNames = map[Type]string{
+	TypeA:     "TypeA",
+	TypeNS:    "TypeNS",
+	TypeCNAME: "TypeCNAME",
+	TypeSOA:   "TypeSOA",
+	TypePTR:   "TypePTR",
+	TypeMX:    "TypeMX",
+	TypeTXT:   "TypeTXT",
+	TypeAAAA:  "TypeAAAA",
+	TypeSRV:   "TypeSRV",
+	TypeOPT:   "TypeOPT",
+	TypeWKS:   "TypeWKS",
+	TypeHINFO: "TypeHINFO",
+	TypeMINFO: "TypeMINFO",
+	TypeAXFR:  "TypeAXFR",
+	TypeALL:   "TypeALL",
+}
+
+// String implements fmt.Stringer.String.
+func (t Type) String() string {
+	if n, ok := typeNames[t]; ok {
+		return n
+	}
+	return printUint16(uint16(t))
+}
+
+// GoString implements fmt.GoStringer.GoString.
+func (t Type) GoString() string {
+	if n, ok := typeNames[t]; ok {
+		return "dnsmessage." + n
+	}
+	return printUint16(uint16(t))
+}
+
+// A Class is a type of network.
+type Class uint16
+
+const (
 	// ResourceHeader.Class and Question.Class
 	ClassINET   Class = 1
 	ClassCSNET  Class = 2
@@ -59,7 +88,44 @@
 
 	// Question.Class
 	ClassANY Class = 255
+)
 
+var classNames = map[Class]string{
+	ClassINET:   "ClassINET",
+	ClassCSNET:  "ClassCSNET",
+	ClassCHAOS:  "ClassCHAOS",
+	ClassHESIOD: "ClassHESIOD",
+	ClassANY:    "ClassANY",
+}
+
+// String implements fmt.Stringer.String.
+func (c Class) String() string {
+	if n, ok := classNames[c]; ok {
+		return n
+	}
+	return printUint16(uint16(c))
+}
+
+// GoString implements fmt.GoStringer.GoString.
+func (c Class) GoString() string {
+	if n, ok := classNames[c]; ok {
+		return "dnsmessage." + n
+	}
+	return printUint16(uint16(c))
+}
+
+// An OpCode is a DNS operation code.
+type OpCode uint16
+
+// GoString implements fmt.GoStringer.GoString.
+func (o OpCode) GoString() string {
+	return printUint16(uint16(o))
+}
+
+// An RCode is a DNS response status code.
+type RCode uint16
+
+const (
 	// Message.Rcode
 	RCodeSuccess        RCode = 0
 	RCodeFormatError    RCode = 1
@@ -69,6 +135,116 @@
 	RCodeRefused        RCode = 5
 )
 
+var rCodeNames = map[RCode]string{
+	RCodeSuccess:        "RCodeSuccess",
+	RCodeFormatError:    "RCodeFormatError",
+	RCodeServerFailure:  "RCodeServerFailure",
+	RCodeNameError:      "RCodeNameError",
+	RCodeNotImplemented: "RCodeNotImplemented",
+	RCodeRefused:        "RCodeRefused",
+}
+
+// String implements fmt.Stringer.String.
+func (r RCode) String() string {
+	if n, ok := rCodeNames[r]; ok {
+		return n
+	}
+	return printUint16(uint16(r))
+}
+
+// GoString implements fmt.GoStringer.GoString.
+func (r RCode) GoString() string {
+	if n, ok := rCodeNames[r]; ok {
+		return "dnsmessage." + n
+	}
+	return printUint16(uint16(r))
+}
+
+func printPaddedUint8(i uint8) string {
+	b := byte(i)
+	return string([]byte{
+		b/100 + '0',
+		b/10%10 + '0',
+		b%10 + '0',
+	})
+}
+
+func printUint8Bytes(buf []byte, i uint8) []byte {
+	b := byte(i)
+	if i >= 100 {
+		buf = append(buf, b/100+'0')
+	}
+	if i >= 10 {
+		buf = append(buf, b/10%10+'0')
+	}
+	return append(buf, b%10+'0')
+}
+
+func printByteSlice(b []byte) string {
+	if len(b) == 0 {
+		return ""
+	}
+	buf := make([]byte, 0, 5*len(b))
+	buf = printUint8Bytes(buf, uint8(b[0]))
+	for _, n := range b[1:] {
+		buf = append(buf, ',', ' ')
+		buf = printUint8Bytes(buf, uint8(n))
+	}
+	return string(buf)
+}
+
+const hexDigits = "0123456789abcdef"
+
+func printString(str []byte) string {
+	buf := make([]byte, 0, len(str))
+	for i := 0; i < len(str); i++ {
+		c := str[i]
+		if c == '.' || c == '-' || c == ' ' ||
+			'A' <= c && c <= 'Z' ||
+			'a' <= c && c <= 'z' ||
+			'0' <= c && c <= '9' {
+			buf = append(buf, c)
+			continue
+		}
+
+		upper := c >> 4
+		lower := (c << 4) >> 4
+		buf = append(
+			buf,
+			'\\',
+			'x',
+			hexDigits[upper],
+			hexDigits[lower],
+		)
+	}
+	return string(buf)
+}
+
+func printUint16(i uint16) string {
+	return printUint32(uint32(i))
+}
+
+func printUint32(i uint32) string {
+	// Max value is 4294967295.
+	buf := make([]byte, 10)
+	for b, d := buf, uint32(1000000000); d > 0; d /= 10 {
+		b[0] = byte(i/d%10 + '0')
+		if b[0] == '0' && len(b) == len(buf) && len(buf) > 1 {
+			buf = buf[1:]
+		}
+		b = b[1:]
+		i %= d
+	}
+	return string(buf)
+}
+
+func printBool(b bool) string {
+	if b {
+		return "true"
+	}
+	return "false"
+}
+
 var (
 	// ErrNotStarted indicates that the prerequisite information isn't
 	// available yet because the previous records haven't been appropriately
@@ -165,6 +341,19 @@
 	return
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (m *Header) GoString() string {
+	return "dnsmessage.Header{" +
+		"ID: " + printUint16(m.ID) + ", " +
+		"Response: " + printBool(m.Response) + ", " +
+		"OpCode: " + m.OpCode.GoString() + ", " +
+		"Authoritative: " + printBool(m.Authoritative) + ", " +
+		"Truncated: " + printBool(m.Truncated) + ", " +
+		"RecursionDesired: " + printBool(m.RecursionDesired) + ", " +
+		"RecursionAvailable: " + printBool(m.RecursionAvailable) + ", " +
+		"RCode: " + m.RCode.GoString() + "}"
+}
+
 // Message is a representation of a DNS message.
 type Message struct {
 	Header
@@ -277,6 +466,13 @@
 	Body   ResourceBody
 }
 
+func (r *Resource) GoString() string {
+	return "dnsmessage.Resource{" +
+		"Header: " + r.Header.GoString() +
+		", Body: &" + r.Body.GoString() +
+		"}"
+}
+
 // A ResourceBody is a DNS resource record minus the header.
 type ResourceBody interface {
 	// pack packs a Resource except for its header.
@@ -285,6 +481,9 @@
 	// realType returns the actual type of the Resource. This is used to
 	// fill in the header Type field.
 	realType() Type
+
+	// GoString implements fmt.GoStringer.GoString.
+	GoString() string
 }
 
 // pack appends the wire format of the Resource to msg.
@@ -919,6 +1118,40 @@
 	return msg, nil
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (m *Message) GoString() string {
+	s := "dnsmessage.Message{Header: " + m.Header.GoString() + ", " +
+		"Questions: []dnsmessage.Question{"
+	if len(m.Questions) > 0 {
+		s += m.Questions[0].GoString()
+		for _, q := range m.Questions[1:] {
+			s += ", " + q.GoString()
+		}
+	}
+	s += "}, Answers: []dnsmessage.Resource{"
+	if len(m.Answers) > 0 {
+		s += m.Answers[0].GoString()
+		for _, a := range m.Answers[1:] {
+			s += ", " + a.GoString()
+		}
+	}
+	s += "}, Authorities: []dnsmessage.Resource{"
+	if len(m.Authorities) > 0 {
+		s += m.Authorities[0].GoString()
+		for _, a := range m.Authorities[1:] {
+			s += ", " + a.GoString()
+		}
+	}
+	s += "}, Additionals: []dnsmessage.Resource{"
+	if len(m.Additionals) > 0 {
+		s += m.Additionals[0].GoString()
+		for _, a := range m.Additionals[1:] {
+			s += ", " + a.GoString()
+		}
+	}
+	return s + "}}"
+}
+
 // A Builder allows incrementally packing a DNS message.
 //
 // Example usage:
@@ -1361,6 +1594,16 @@
 	Length uint16
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (h *ResourceHeader) GoString() string {
+	return "dnsmessage.ResourceHeader{" +
+		"Name: " + h.Name.GoString() + ", " +
+		"Type: " + h.Type.GoString() + ", " +
+		"Class: " + h.Class.GoString() + ", " +
+		"TTL: " + printUint32(h.TTL) + ", " +
+		"Length: " + printUint16(h.Length) + "}"
+}
+
 // pack appends the wire format of the ResourceHeader to oldMsg.
 //
 // The bytes where length was packed are returned as a slice so they can be
@@ -1623,10 +1866,25 @@
 	return n, nil
 }
 
+// MustNewName creates a new Name from a string and panics on error.
+func MustNewName(name string) Name {
+	n, err := NewName(name)
+	if err != nil {
+		panic("creating name: " + err.Error())
+	}
+	return n
+}
+
+// String implements fmt.Stringer.String.
 func (n Name) String() string {
 	return string(n.Data[:n.Length])
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (n *Name) GoString() string {
+	return `dnsmessage.MustNewName("` + printString(n.Data[:n.Length]) + `")`
+}
+
 // pack appends the wire format of the Name to msg.
 //
 // Domain names are a sequence of counted strings split at the dots. They end
@@ -1826,6 +2084,14 @@
 	return packClass(msg, q.Class), nil
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (q *Question) GoString() string {
+	return "dnsmessage.Question{" +
+		"Name: " + q.Name.GoString() + ", " +
+		"Type: " + q.Type.GoString() + ", " +
+		"Class: " + q.Class.GoString() + "}"
+}
+
 func unpackResourceBody(msg []byte, off int, hdr ResourceHeader) (ResourceBody, int, error) {
 	var (
 		r    ResourceBody
@@ -1907,6 +2173,11 @@
 	return r.CNAME.pack(msg, compression, compressionOff)
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (r *CNAMEResource) GoString() string {
+	return "dnsmessage.CNAMEResource{CNAME: " + r.CNAME.GoString() + "}"
+}
+
 func unpackCNAMEResource(msg []byte, off int) (CNAMEResource, error) {
 	var cname Name
 	if _, err := cname.unpack(msg, off); err != nil {
@@ -1936,6 +2207,13 @@
 	return msg, nil
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (r *MXResource) GoString() string {
+	return "dnsmessage.MXResource{" +
+		"Pref: " + printUint16(r.Pref) + ", " +
+		"MX: " + r.MX.GoString() + "}"
+}
+
 func unpackMXResource(msg []byte, off int) (MXResource, error) {
 	pref, off, err := unpackUint16(msg, off)
 	if err != nil {
@@ -1962,6 +2240,11 @@
 	return r.NS.pack(msg, compression, compressionOff)
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (r *NSResource) GoString() string {
+	return "dnsmessage.NSResource{NS: " + r.NS.GoString() + "}"
+}
+
 func unpackNSResource(msg []byte, off int) (NSResource, error) {
 	var ns Name
 	if _, err := ns.unpack(msg, off); err != nil {
@@ -1984,6 +2267,11 @@
 	return r.PTR.pack(msg, compression, compressionOff)
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (r *PTRResource) GoString() string {
+	return "dnsmessage.PTRResource{PTR: " + r.PTR.GoString() + "}"
+}
+
 func unpackPTRResource(msg []byte, off int) (PTRResource, error) {
 	var ptr Name
 	if _, err := ptr.unpack(msg, off); err != nil {
@@ -2029,6 +2317,18 @@
 	return packUint32(msg, r.MinTTL), nil
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (r *SOAResource) GoString() string {
+	return "dnsmessage.SOAResource{" +
+		"NS: " + r.NS.GoString() + ", " +
+		"MBox: " + r.MBox.GoString() + ", " +
+		"Serial: " + printUint32(r.Serial) + ", " +
+		"Refresh: " + printUint32(r.Refresh) + ", " +
+		"Retry: " + printUint32(r.Retry) + ", " +
+		"Expire: " + printUint32(r.Expire) + ", " +
+		"MinTTL: " + printUint32(r.MinTTL) + "}"
+}
+
 func unpackSOAResource(msg []byte, off int) (SOAResource, error) {
 	var ns Name
 	off, err := ns.unpack(msg, off)
@@ -2084,6 +2384,19 @@
 	return msg, nil
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (r *TXTResource) GoString() string {
+	s := "dnsmessage.TXTResource{TXT: []string{"
+	if len(r.TXT) == 0 {
+		return s + "}}"
+	}
+	s += `"` + printString([]byte(r.TXT[0]))
+	for _, t := range r.TXT[1:] {
+		s += `", "` + printString([]byte(t))
+	}
+	return s + `"}}`
+}
+
 func unpackTXTResource(msg []byte, off int, length uint16) (TXTResource, error) {
 	txts := make([]string, 0, 1)
 	for n := uint16(0); n < length; {
@@ -2127,6 +2440,15 @@
 	return msg, nil
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (r *SRVResource) GoString() string {
+	return "dnsmessage.SRVResource{" +
+		"Priority: " + printUint16(r.Priority) + ", " +
+		"Weight: " + printUint16(r.Weight) + ", " +
+		"Port: " + printUint16(r.Port) + ", " +
+		"Target: " + r.Target.GoString() + "}"
+}
+
 func unpackSRVResource(msg []byte, off int) (SRVResource, error) {
 	priority, off, err := unpackUint16(msg, off)
 	if err != nil {
@@ -2161,6 +2483,12 @@
 	return packBytes(msg, r.A[:]), nil
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (r *AResource) GoString() string {
+	return "dnsmessage.AResource{" +
+		"A: [4]byte{" + printByteSlice(r.A[:]) + "}}"
+}
+
 func unpackAResource(msg []byte, off int) (AResource, error) {
 	var a [4]byte
 	if _, err := unpackBytes(msg, off, a[:]); err != nil {
@@ -2178,6 +2506,12 @@
 	return TypeAAAA
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (r *AAAAResource) GoString() string {
+	return "dnsmessage.AAAAResource{" +
+		"AAAA: [16]byte{" + printByteSlice(r.AAAA[:]) + "}}"
+}
+
 // pack appends the wire format of the AAAAResource to msg.
 func (r *AAAAResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
 	return packBytes(msg, r.AAAA[:]), nil
@@ -2208,6 +2542,13 @@
 	Data []byte
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (o *Option) GoString() string {
+	return "dnsmessage.Option{" +
+		"Code: " + printUint16(o.Code) + ", " +
+		"Data: []byte{" + printByteSlice(o.Data) + "}}"
+}
+
 func (r *OPTResource) realType() Type {
 	return TypeOPT
 }
@@ -2222,6 +2563,19 @@
 	return msg, nil
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (r *OPTResource) GoString() string {
+	s := "dnsmessage.OPTResource{Options: []dnsmessage.Option{"
+	if len(r.Options) == 0 {
+		return s + "}}"
+	}
+	s += r.Options[0].GoString()
+	for _, o := range r.Options[1:] {
+		s += ", " + o.GoString()
+	}
+	return s + "}}"
+}
+
 func unpackOPTResource(msg []byte, off int, length uint16) (OPTResource, error) {
 	var opts []Option
 	for oldOff := off; off < oldOff+int(length); {
diff --git a/dns/dnsmessage/message_test.go b/dns/dnsmessage/message_test.go
index 7e4e4bd..ab48746 100644
--- a/dns/dnsmessage/message_test.go
+++ b/dns/dnsmessage/message_test.go
@@ -12,12 +12,97 @@
 	"testing"
 )
 
-func mustNewName(name string) Name {
-	n, err := NewName(name)
-	if err != nil {
-		panic(err)
+func TestPrintPaddedUint8(t *testing.T) {
+	tests := []struct {
+		num  uint8
+		want string
+	}{
+		{0, "000"},
+		{1, "001"},
+		{9, "009"},
+		{10, "010"},
+		{99, "099"},
+		{100, "100"},
+		{124, "124"},
+		{104, "104"},
+		{120, "120"},
+		{255, "255"},
 	}
-	return n
+
+	for _, test := range tests {
+		if got := printPaddedUint8(test.num); got != test.want {
+			t.Errorf("got printPaddedUint8(%d) = %s, want = %s", test.num, got, test.want)
+		}
+	}
+}
+
+func TestPrintUint8Bytes(t *testing.T) {
+	tests := []uint8{
+		0,
+		1,
+		9,
+		10,
+		99,
+		100,
+		124,
+		104,
+		120,
+		255,
+	}
+
+	for _, test := range tests {
+		if got, want := string(printUint8Bytes(nil, test)), fmt.Sprint(test); got != want {
+			t.Errorf("got printUint8Bytes(%d) = %s, want = %s", test, got, want)
+		}
+	}
+}
+
+func TestPrintUint16(t *testing.T) {
+	tests := []uint16{
+		65535,
+		0,
+		1,
+		10,
+		100,
+		1000,
+		10000,
+		324,
+		304,
+		320,
+	}
+
+	for _, test := range tests {
+		if got, want := printUint16(test), fmt.Sprint(test); got != want {
+			t.Errorf("got printUint16(%d) = %s, want = %s", test, got, want)
+		}
+	}
+}
+
+func TestPrintUint32(t *testing.T) {
+	tests := []uint32{
+		4294967295,
+		65535,
+		0,
+		1,
+		10,
+		100,
+		1000,
+		10000,
+		100000,
+		1000000,
+		10000000,
+		100000000,
+		1000000000,
+		324,
+		304,
+		320,
+	}
+
+	for _, test := range tests {
+		if got, want := printUint32(test), fmt.Sprint(test); got != want {
+			t.Errorf("got printUint32(%d) = %s, want = %s", test, got, want)
+		}
+	}
 }
 
 func mustEDNS0ResourceHeader(l int, extrc RCode, do bool) ResourceHeader {
@@ -59,7 +144,7 @@
 
 func TestNameString(t *testing.T) {
 	want := "foo"
-	name := mustNewName(want)
+	name := MustNewName(want)
 	if got := fmt.Sprint(name); got != want {
 		t.Errorf("got fmt.Sprint(%#v) = %s, want = %s", name, got, want)
 	}
@@ -67,7 +152,7 @@
 
 func TestQuestionPackUnpack(t *testing.T) {
 	want := Question{
-		Name:  mustNewName("."),
+		Name:  MustNewName("."),
 		Type:  TypeA,
 		Class: ClassINET,
 	}
@@ -136,8 +221,8 @@
 	}
 
 	for _, test := range tests {
-		in := mustNewName(test.in)
-		want := mustNewName(test.want)
+		in := MustNewName(test.in)
+		want := MustNewName(test.want)
 		buf, err := in.pack(make([]byte, 0, 30), map[string]int{}, 0)
 		if err != test.err {
 			t.Errorf("got %q.pack() = %v, want = %v", test.in, err, test.err)
@@ -167,7 +252,7 @@
 }
 
 func TestIncompressibleName(t *testing.T) {
-	name := mustNewName("example.com.")
+	name := MustNewName("example.com.")
 	compression := map[string]int{}
 	buf, err := name.pack(make([]byte, 0, 100), compression, 0)
 	if err != nil {
@@ -252,7 +337,7 @@
 		{
 			Questions: []Question{
 				{
-					Name:  mustNewName("."),
+					Name:  MustNewName("."),
 					Type:  TypeAAAA,
 					Class: ClassINET,
 				},
@@ -284,7 +369,7 @@
 		{
 			Questions: []Question{
 				{
-					Name:  mustNewName("."),
+					Name:  MustNewName("."),
 					Type:  TypeAAAA,
 					Class: ClassINET,
 				},
@@ -471,7 +556,7 @@
 func TestVeryLongTxt(t *testing.T) {
 	want := Resource{
 		ResourceHeader{
-			Name:  mustNewName("foo.bar.example.com."),
+			Name:  MustNewName("foo.bar.example.com."),
 			Type:  TypeTXT,
 			Class: ClassINET,
 		},
@@ -714,7 +799,7 @@
 			Message{
 				Questions: []Question{
 					{
-						Name:  mustNewName("."),
+						Name:  MustNewName("."),
 						Type:  TypeAAAA,
 						Class: ClassINET,
 					},
@@ -727,7 +812,7 @@
 			Message{
 				Questions: []Question{
 					{
-						Name:  mustNewName("."),
+						Name:  MustNewName("."),
 						Type:  TypeAAAA,
 						Class: ClassINET,
 					},
@@ -744,7 +829,7 @@
 			Message{
 				Questions: []Question{
 					{
-						Name:  mustNewName("."),
+						Name:  MustNewName("."),
 						Type:  TypeA,
 						Class: ClassINET,
 					},
@@ -779,7 +864,7 @@
 				Header: Header{RCode: RCodeFormatError},
 				Questions: []Question{
 					{
-						Name:  mustNewName("."),
+						Name:  MustNewName("."),
 						Type:  TypeA,
 						Class: ClassINET,
 					},
@@ -805,7 +890,7 @@
 				Header: Header{RCode: RCodeServerFailure},
 				Questions: []Question{
 					{
-						Name:  mustNewName("."),
+						Name:  MustNewName("."),
 						Type:  TypeAAAA,
 						Class: ClassINET,
 					},
@@ -847,7 +932,7 @@
 				Header: Header{RCode: RCodeNameError},
 				Questions: []Question{
 					{
-						Name:  mustNewName("."),
+						Name:  MustNewName("."),
 						Type:  TypeAAAA,
 						Class: ClassINET,
 					},
@@ -900,8 +985,28 @@
 	}
 }
 
+// TestGoString tests that Message.GoString produces Go code that compiles to
+// reproduce the Message.
+//
+// This test was produced as follows:
+// 1. Run (*Message).GoString on largeTestMsg().
+// 2. Remove "dnsmessage." from the output.
+// 3. Paste the result in the test to store it in msg.
+// 4. Also put the original output in the test to store in want.
+func TestGoString(t *testing.T) {
+	msg := Message{Header: Header{ID: 0, Response: true, OpCode: 0, Authoritative: true, Truncated: false, RecursionDesired: false, RecursionAvailable: false, RCode: RCodeSuccess}, Questions: []Question{Question{Name: MustNewName("foo.bar.example.com."), Type: TypeA, Class: ClassINET}}, Answers: []Resource{Resource{Header: ResourceHeader{Name: MustNewName("foo.bar.example.com."), Type: TypeA, Class: ClassINET, TTL: 0, Length: 0}, Body: &AResource{A: [4]byte{127, 0, 0, 1}}}, Resource{Header: ResourceHeader{Name: MustNewName("foo.bar.example.com."), Type: TypeA, Class: ClassINET, TTL: 0, Length: 0}, Body: &AResource{A: [4]byte{127, 0, 0, 2}}}, Resource{Header: ResourceHeader{Name: MustNewName("foo.bar.example.com."), Type: TypeAAAA, Class: ClassINET, TTL: 0, Length: 0}, Body: &AAAAResource{AAAA: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}}}, Resource{Header: ResourceHeader{Name: MustNewName("foo.bar.example.com."), Type: TypeCNAME, Class: ClassINET, TTL: 0, Length: 0}, Body: &CNAMEResource{CNAME: MustNewName("alias.example.com.")}}, Resource{Header: ResourceHeader{Name: MustNewName("foo.bar.example.com."), Type: TypeSOA, Class: ClassINET, TTL: 0, Length: 0}, Body: &SOAResource{NS: MustNewName("ns1.example.com."), MBox: MustNewName("mb.example.com."), Serial: 1, Refresh: 2, Retry: 3, Expire: 4, MinTTL: 5}}, Resource{Header: ResourceHeader{Name: MustNewName("foo.bar.example.com."), Type: TypePTR, Class: ClassINET, TTL: 0, Length: 0}, Body: &PTRResource{PTR: MustNewName("ptr.example.com.")}}, Resource{Header: ResourceHeader{Name: MustNewName("foo.bar.example.com."), Type: TypeMX, Class: ClassINET, TTL: 0, Length: 0}, Body: &MXResource{Pref: 7, MX: MustNewName("mx.example.com.")}}, Resource{Header: ResourceHeader{Name: MustNewName("foo.bar.example.com."), Type: TypeSRV, Class: ClassINET, TTL: 0, Length: 0}, Body: &SRVResource{Priority: 8, Weight: 9, Port: 11, Target: MustNewName("srv.example.com.")}}}, Authorities: []Resource{Resource{Header: ResourceHeader{Name: MustNewName("foo.bar.example.com."), Type: TypeNS, Class: ClassINET, TTL: 0, Length: 0}, Body: &NSResource{NS: MustNewName("ns1.example.com.")}}, Resource{Header: ResourceHeader{Name: MustNewName("foo.bar.example.com."), Type: TypeNS, Class: ClassINET, TTL: 0, Length: 0}, Body: &NSResource{NS: MustNewName("ns2.example.com.")}}}, Additionals: []Resource{Resource{Header: ResourceHeader{Name: MustNewName("foo.bar.example.com."), Type: TypeTXT, Class: ClassINET, TTL: 0, Length: 0}, Body: &TXTResource{TXT: []string{"So Long\x2c and Thanks for All the Fish"}}}, Resource{Header: ResourceHeader{Name: MustNewName("foo.bar.example.com."), Type: TypeTXT, Class: ClassINET, TTL: 0, Length: 0}, Body: &TXTResource{TXT: []string{"Hamster Huey and the Gooey Kablooie"}}}, Resource{Header: ResourceHeader{Name: MustNewName("."), Type: TypeOPT, Class: 4096, TTL: 4261412864, Length: 0}, Body: &OPTResource{Options: []Option{Option{Code: 10, Data: []byte{1, 35, 69, 103, 137, 171, 205, 239}}}}}}}
+	if !reflect.DeepEqual(msg, largeTestMsg()) {
+		t.Error("Message.GoString lost information or largeTestMsg changed: msg != largeTestMsg()")
+	}
+	got := msg.GoString()
+	want := `dnsmessage.Message{Header: dnsmessage.Header{ID: 0, Response: true, OpCode: 0, Authoritative: true, Truncated: false, RecursionDesired: false, RecursionAvailable: false, RCode: dnsmessage.RCodeSuccess}, Questions: []dnsmessage.Question{dnsmessage.Question{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeA, Class: dnsmessage.ClassINET}}, Answers: []dnsmessage.Resource{dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeA, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.AResource{A: [4]byte{127, 0, 0, 1}}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeA, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.AResource{A: [4]byte{127, 0, 0, 2}}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeAAAA, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.AAAAResource{AAAA: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeCNAME, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.CNAMEResource{CNAME: dnsmessage.MustNewName("alias.example.com.")}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeSOA, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.SOAResource{NS: dnsmessage.MustNewName("ns1.example.com."), MBox: dnsmessage.MustNewName("mb.example.com."), Serial: 1, Refresh: 2, Retry: 3, Expire: 4, MinTTL: 5}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypePTR, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.PTRResource{PTR: dnsmessage.MustNewName("ptr.example.com.")}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeMX, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.MXResource{Pref: 7, MX: dnsmessage.MustNewName("mx.example.com.")}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeSRV, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.SRVResource{Priority: 8, Weight: 9, Port: 11, Target: dnsmessage.MustNewName("srv.example.com.")}}}, Authorities: []dnsmessage.Resource{dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeNS, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.NSResource{NS: dnsmessage.MustNewName("ns1.example.com.")}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeNS, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.NSResource{NS: dnsmessage.MustNewName("ns2.example.com.")}}}, Additionals: []dnsmessage.Resource{dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeTXT, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.TXTResource{TXT: []string{"So Long\x2c and Thanks for All the Fish"}}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeTXT, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.TXTResource{TXT: []string{"Hamster Huey and the Gooey Kablooie"}}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("."), Type: dnsmessage.TypeOPT, Class: 4096, TTL: 4261412864, Length: 0}, Body: &dnsmessage.OPTResource{Options: []dnsmessage.Option{dnsmessage.Option{Code: 10, Data: []byte{1, 35, 69, 103, 137, 171, 205, 239}}}}}}}`
+	if got != want {
+		t.Errorf("got msg1.GoString() = %s\nwant = %s", got, want)
+	}
+}
+
 func benchmarkParsingSetup() ([]byte, error) {
-	name := mustNewName("foo.bar.example.com.")
+	name := MustNewName("foo.bar.example.com.")
 	msg := Message{
 		Header: Header{Response: true, Authoritative: true},
 		Questions: []Question{
@@ -1026,7 +1131,7 @@
 }
 
 func benchmarkBuildingSetup() (Name, []byte) {
-	name := mustNewName("foo.bar.example.com.")
+	name := MustNewName("foo.bar.example.com.")
 	buf := make([]byte, 0, packStartingCap)
 	return name, buf
 }
@@ -1104,7 +1209,7 @@
 }
 
 func smallTestMsg() Message {
-	name := mustNewName("example.com.")
+	name := MustNewName("example.com.")
 	return Message{
 		Header: Header{Response: true, Authoritative: true},
 		Questions: []Question{
@@ -1173,7 +1278,7 @@
 }
 
 func largeTestMsg() Message {
-	name := mustNewName("foo.bar.example.com.")
+	name := MustNewName("foo.bar.example.com.")
 	return Message{
 		Header: Header{Response: true, Authoritative: true},
 		Questions: []Question{
@@ -1214,7 +1319,7 @@
 					Type:  TypeCNAME,
 					Class: ClassINET,
 				},
-				&CNAMEResource{mustNewName("alias.example.com.")},
+				&CNAMEResource{MustNewName("alias.example.com.")},
 			},
 			{
 				ResourceHeader{
@@ -1223,8 +1328,8 @@
 					Class: ClassINET,
 				},
 				&SOAResource{
-					NS:      mustNewName("ns1.example.com."),
-					MBox:    mustNewName("mb.example.com."),
+					NS:      MustNewName("ns1.example.com."),
+					MBox:    MustNewName("mb.example.com."),
 					Serial:  1,
 					Refresh: 2,
 					Retry:   3,
@@ -1238,7 +1343,7 @@
 					Type:  TypePTR,
 					Class: ClassINET,
 				},
-				&PTRResource{mustNewName("ptr.example.com.")},
+				&PTRResource{MustNewName("ptr.example.com.")},
 			},
 			{
 				ResourceHeader{
@@ -1248,7 +1353,7 @@
 				},
 				&MXResource{
 					7,
-					mustNewName("mx.example.com."),
+					MustNewName("mx.example.com."),
 				},
 			},
 			{
@@ -1261,7 +1366,7 @@
 					8,
 					9,
 					11,
-					mustNewName("srv.example.com."),
+					MustNewName("srv.example.com."),
 				},
 			},
 		},
@@ -1272,7 +1377,7 @@
 					Type:  TypeNS,
 					Class: ClassINET,
 				},
-				&NSResource{mustNewName("ns1.example.com.")},
+				&NSResource{MustNewName("ns1.example.com.")},
 			},
 			{
 				ResourceHeader{
@@ -1280,7 +1385,7 @@
 					Type:  TypeNS,
 					Class: ClassINET,
 				},
-				&NSResource{mustNewName("ns2.example.com.")},
+				&NSResource{MustNewName("ns2.example.com.")},
 			},
 		},
 		Additionals: []Resource{