diff --git a/src/encoding/xml/marshal_test.go b/src/encoding/xml/marshal_test.go index 31309ef2ca8c5a..56347e1212c27b 100644 --- a/src/encoding/xml/marshal_test.go +++ b/src/encoding/xml/marshal_test.go @@ -133,6 +133,14 @@ type Service struct { Extra2 interface{} `xml:"host>extra2"` } +type ServiceErrorNameConflict struct { + XMLName struct{} `xml:"service"` + Domain *Domain `xml:"host>domain"` + Port *Port `xml:"host>portZZZ"` + Extra1 interface{} + Extra2 interface{} `xml:"host>extra2"` +} + var nilStruct *Ship type EmbedA struct { @@ -189,6 +197,10 @@ type NameInField struct { Foo Name `xml:"ns foo"` } +type NameInFieldError struct { + Foo Name `xml:"ns "` +} + type AttrTest struct { Int int `xml:",attr"` Named int `xml:"int,attr"` @@ -320,9 +332,22 @@ type MyMarshalerTest struct { var _ Marshaler = (*MyMarshalerTest)(nil) func (m *MyMarshalerTest) MarshalXML(e *Encoder, start StartElement) error { + return e.EncodeElement("hello world", start) +} + +type MyMarshalerTestErrorMissingName struct { +} + +func (m *MyMarshalerTestErrorMissingName) MarshalXML(e *Encoder, _ StartElement) error { + return e.EncodeElement("hello world", StartElement{Name{"", ""}, nil}) +} + +type MyMarshalerTestErrorNotClosed struct { +} + +func (m *MyMarshalerTestErrorNotClosed) MarshalXML(e *Encoder, start StartElement) error { e.EncodeToken(start) - e.EncodeToken(CharData([]byte("hello world"))) - e.EncodeToken(EndElement{start.Name}) + e.EncodeToken(CharData("hello world")) return nil } @@ -791,6 +816,12 @@ var marshalTests = []struct { ``, MarshalOnly: true, }, + { + Value: &ServiceErrorNameConflict{Port: &Port{Number: "80"}}, + ExpectXML: `80`, + MarshalError: `conflicts with name`, + UnmarshalError: `conflicts with name`, + }, { Value: &struct { XMLName struct{} `xml:"space top"` @@ -955,6 +986,12 @@ var marshalTests = []struct { ExpectXML: ``, UnmarshalOnly: true, }, + { + Value: &NameInFieldError{Name{Space: "ns", Local: "foo"}}, + ExpectXML: ``, + MarshalError: "xml: namespace without name in field", + UnmarshalError: "xml: namespace without name in field", + }, // Marshaling zero xml.Name uses the tag or field name. { @@ -1243,6 +1280,16 @@ var marshalTests = []struct { ExpectXML: `hello world`, Value: &MyMarshalerTest{}, }, + { + ExpectXML: `hello world`, + Value: &MyMarshalerTestErrorMissingName{}, + MarshalError: "xml: EncodeElement of StartElement with missing name", + }, + { + ExpectXML: `hello world`, + Value: &MyMarshalerTestErrorNotClosed{}, + MarshalError: "MarshalXML wrote invalid XML", + }, { ExpectXML: ``, Value: &MarshalerStruct{}, @@ -1694,6 +1741,27 @@ type BadAttr struct { Name map[string]string `xml:"name,attr"` } +type BadTag struct { + Comment string `xml:",comment,omitempty"` +} + +type BadTagMultipleModes struct { + Comment string `xml:",attr,comment"` +} + +type BadTagTrailingTag struct { + Comment string `xml:"comment>"` +} + +type BadEmbed struct { + BadInnerEmbed +} + +type BadInnerEmbed struct { + Field string `xml:"Field,attr,comment"` +} + +// used by both TestMarshalErrors and TestMarshalIndentErrors var marshalErrorTests = []struct { Value interface{} Err string @@ -1730,6 +1798,22 @@ var marshalErrorTests = []struct { Value: BadAttr{map[string]string{"X": "Y"}}, Err: `xml: unsupported type: map[string]string`, }, + { + Value: BadTag{"some comment"}, + Err: `xml: invalid tag in field Comment of type xml.BadTag: ",comment,omitempty"`, + }, + { + Value: BadTagMultipleModes{"some comment"}, + Err: `xml: invalid tag in field Comment of type xml.BadTagMultipleModes: ",attr,comment"`, + }, + { + Value: BadTagTrailingTag{"some comment"}, + Err: `xml: trailing '>' in field Comment of type xml.BadTagTrailingTag`, + }, + { + Value: BadEmbed{}, + Err: `xml: invalid tag in field Field of type xml.BadInnerEmbed: "Field,attr,comment"`, + }, } var marshalIndentTests = []struct { @@ -1829,6 +1913,24 @@ func TestMarshalIndent(t *testing.T) { } } +func TestMarshalIndentErrors(t *testing.T) { + for idx, test := range marshalErrorTests { + data, err := MarshalIndent(test.Value, "", "") + if err == nil { + t.Errorf("#%d: marshalIndent(%#v) = [success] %q, want error %v", idx, test.Value, data, test.Err) + continue + } + if err.Error() != test.Err { + t.Errorf("#%d: marshalIndent(%#v) = [error] %v, want %v", idx, test.Value, err, test.Err) + } + if test.Kind != reflect.Invalid { + if kind := err.(*UnsupportedTypeError).Type.Kind(); kind != test.Kind { + t.Errorf("#%d: marshalIndent(%#v) = [error kind] %s, want %s", idx, test.Value, kind, test.Kind) + } + } + } +} + type limitedBytesWriter struct { w io.Writer remain int // until writes fail @@ -2020,6 +2122,12 @@ var encodeTokenTests = []struct { ProcInst{"", []byte("Instruction?>")}, }, err: "xml: EncodeToken of ProcInst with invalid Target", +}, { + desc: "proc instruction with endProcInst", + toks: []Token{ + ProcInst{"Target", []byte("Instruction?>")}, + }, + err: "xml: EncodeToken of ProcInst containing ?> marker", }, { desc: "directive", toks: []Token{ @@ -2340,6 +2448,28 @@ func TestProcInstEncodeToken(t *testing.T) { } } +func TestProcInstEncodeTokenBadNameString(t *testing.T) { + tests := []struct { + input ProcInst + }{ + {ProcInst{"\xe6", []byte("Instruction")}}, + {ProcInst{".", []byte("Instruction")}}, + {ProcInst{"a\xe6", []byte("Instruction")}}, + } + expectedError := `xml: EncodeToken of ProcInst with invalid Target` + + for _, test := range tests { + var buf bytes.Buffer + enc := NewEncoder(&buf) + + if err := enc.EncodeToken(test.input); err == nil { + t.Fatalf(`expected not receive expected error for input %s`, test.input) + } else if err.Error() != expectedError { + t.Fatalf(`err.Error() = "%s", want "%s"`, err.Error(), expectedError) + } + } +} + func TestDecodeEncode(t *testing.T) { var in, out bytes.Buffer in.WriteString(` @@ -2449,7 +2579,6 @@ func TestIssue16158(t *testing.T) { } // Issue 20953. Crash on invalid XMLName attribute. - type InvalidXMLName struct { XMLName Name `xml:"error"` Type struct { diff --git a/src/encoding/xml/read_test.go b/src/encoding/xml/read_test.go index 8c2e70fa22ee68..bd515d4b532403 100644 --- a/src/encoding/xml/read_test.go +++ b/src/encoding/xml/read_test.go @@ -5,6 +5,7 @@ package xml import ( + "errors" "io" "reflect" "strings" @@ -332,7 +333,7 @@ var badPathTests = []struct { func TestUnmarshalBadPaths(t *testing.T) { for _, tt := range badPathTests { err := Unmarshal([]byte(pathTestString), tt.v) - if !reflect.DeepEqual(err, tt.e) { + if !strings.Contains(err.Error(), "conflicts with field") || !reflect.DeepEqual(err, tt.e) { t.Fatalf("Unmarshal with %#v didn't fail properly:\nhave %#v,\nwant %#v", tt.v, err, tt.e) } } @@ -348,6 +349,18 @@ type TestThree struct { Attr string `xml:",attr"` } +func TestUnmarshalNonpointer(t *testing.T) { + var x TestThree + err := Unmarshal([]byte(withoutNameTypeData), x) + errText := "non-pointer passed to Unmarshal" + if err == nil { + t.Errorf("[success], want error %v", errText) + } + if err.Error() != errText { + t.Errorf("[error] %v, want %v", err, errText) + } +} + func TestUnmarshalWithoutNameType(t *testing.T) { var x TestThree if err := Unmarshal([]byte(withoutNameTypeData), &x); err != nil { @@ -686,6 +699,126 @@ func TestUnmarshaler(t *testing.T) { } } +var errText = "UnmarshalXML failed" + +type MyBodyError struct { + Body string +} + +func (m *MyBodyError) UnmarshalXML(*Decoder, StartElement) error { + return errors.New(errText) +} + +func TestUnmarshalerError(t *testing.T) { + xml := `words` + var m MyBodyError + err := Unmarshal([]byte(xml), &m) + + if err == nil { + t.Errorf("[success], want error %v", errText) + } + if err.Error() != errText { + t.Errorf("[error] %v, want %v", err, errText) + } +} + +type MyBodyErrorText struct { + Body string +} + +func (m MyBodyErrorText) UnmarshalText([]byte) error { + return errors.New(errText) +} + +func TestUnmarshalerTextError(t *testing.T) { + xml := `words` + var m MyBodyErrorText + err := Unmarshal([]byte(xml), &m) + + if err == nil { + t.Errorf("[success], want error %v", errText) + } + if err.Error() != errText { + t.Errorf("[error] %v, want %v", err, errText) + } +} + +func TestUnmarshalUnknownType(t *testing.T) { + expectedError := `unknown type func()` + var dst func() + + if err := Unmarshal([]byte(pathTestString), &dst); err == nil || err.Error() != expectedError { + t.Errorf("have %v, want %v", err, expectedError) + } +} + +type XMLNameWithNamespace struct { + XMLName Name `xml:"ns Test3"` +} + +func TestUnmarshalErrors(t *testing.T) { + tests := []struct { + input []byte + expectedError string + }{ + {[]byte(""), "EOF"}, + {[]byte("bbb"), `expected element type but have `}, + {[]byte(`inside`), `expected element in name space ns but have namespace`}, + {[]byte(`inside`), `expected element in name space ns but have no name space`}, + } + for _, test := range tests { + var dst XMLNameWithNamespace + + if err := Unmarshal(test.input, &dst); err == nil || err.Error() != test.expectedError { + t.Errorf("have %v, want %v", err, test.expectedError) + } + } +} + +func TestSkip(t *testing.T) { + xml := "bbbnnnuuuwefwef" + + var dst Person + d := NewDecoder(strings.NewReader(xml)) + + // consume a start element + if _, err := d.Token(); err != nil { + t.Fatalf("expected d.Token() to succeed but got: :%v", err) + } + + // consume the rest until the matching end element + if err := d.Skip(); err != nil { + t.Fatalf("expected d.Skip() to succeed but got: :%v", err) + } + + if err := d.Decode(&dst); err != nil { + t.Fatalf("expected d.Decode() to succeed but got: :%v", err) + } +} + +func TestSkipErrors(t *testing.T) { + tests := []struct { + input string + expectedError string + }{ + {"", `XML syntax error on line 1: unexpected EOF`}, + {"bbbb", `XML syntax error on line 1: unexpected EOF`}, + } + + for _, test := range tests { + d := NewDecoder(strings.NewReader(test.input)) + + // consume a start element + if _, err := d.Token(); err != nil { + t.Fatalf("expected d.Token() to succeed but got: :%v", err) + } + + if err := d.Skip(); err == nil || err.Error() != test.expectedError { + t.Errorf("have %v, want %v", err, test.expectedError) + } + } +} + type Pea struct { Cotelydon string } diff --git a/src/encoding/xml/xml_test.go b/src/encoding/xml/xml_test.go index efddca43e9102e..9ccfabb5776b64 100644 --- a/src/encoding/xml/xml_test.go +++ b/src/encoding/xml/xml_test.go @@ -190,6 +190,7 @@ var xmlInput = []string{ "", @@ -218,6 +219,22 @@ func TestRawToken(t *testing.T) { testRawToken(t, d, testInput, rawTokens) } +// myIOReader does not implement io.ByteReader forcing a code path +// where we wrap the io.Reader in an bufio.NewReader +type myIOReader struct { + input string +} + +func (m myIOReader) Read(p []byte) (int, error) { + return strings.NewReader(m.input).Read(p) +} + +func TestNewDecoder(t *testing.T) { + d := NewDecoder(myIOReader{testInput}) + d.Entity = testEntity + testRawToken(t, d, testInput, rawTokens) +} + const nonStrictInput = ` non&entity &unknown;entity @@ -300,6 +317,48 @@ func TestRawTokenAltEncoding(t *testing.T) { testRawToken(t, d, testInputAltEncoding, rawTokensAltEncoding) } +func TestRawTokenAltEncodingErrors(t *testing.T) { + tests := []struct { + charsetReader func(charset string, input io.Reader) (io.Reader, error) + expectedError string + }{ + { + func(_ string, _ io.Reader) (io.Reader, error) { return nil, fmt.Errorf("terrible") }, + `xml: opening charset "x-testing-uppercase": terrible`, + }, + } + for _, test := range tests { + d := NewDecoder(strings.NewReader(testInputAltEncoding)) + d.CharsetReader = test.charsetReader + + var err error + for _, err = d.Token(); err == nil; _, err = d.Token() { + } + if err.Error() != test.expectedError { + t.Fatalf(`err.Error() = "%s", want "%s"`, err.Error(), test.expectedError) + } + } +} + +func TestRawTokenAltEncodingPanic(t *testing.T) { + defer func() { + expectedError := `CharsetReader returned a nil Reader for charset x-testing-uppercase` + r := recover() + if r == nil { + t.Errorf("code did not panic but should have") + } else if r != expectedError { + t.Fatalf(`panic = "%s", want "%s"`, r, expectedError) + } + }() + + d := NewDecoder(strings.NewReader(testInputAltEncoding)) + d.CharsetReader = func(charset string, input io.Reader) (io.Reader, error) { return nil, nil } + + var err error + for _, err = d.Token(); err == nil; _, err = d.Token() { + } +} + func TestRawTokenAltEncodingNoConverter(t *testing.T) { d := NewDecoder(strings.NewReader(testInputAltEncoding)) token, err := d.RawToken() @@ -430,6 +489,54 @@ func TestToken(t *testing.T) { } } +func TestTokenErrors(t *testing.T) { + tests := []struct { + input string + syntaxError bool + expectedError string + }{ + {``, true, `XML syntax error on line 1: unexpected end element `}, + {``, true, `XML syntax error on line 1: expected target name after `, true, `XML syntax error on line 1: unexpected EOF`}, + {``, false, `xml: unsupported version "1.1"; only version 1.0 is supported`}, + {``, false, `xml: unsupported version "1"; only version 1.0 is supported`}, + {``, false, `xml: unsupported version "wat"; only version 1.0 is supported`}, + {``, false, `xml: encoding "UTF-9000" declared but Decoder.CharsetReader is nil`}, + {`", false, `XML syntax error on line 1: invalid UTF-8`}, + {"", false, "XML syntax error on line 1: invalid XML name: \xe6"}, + {"", false, "XML syntax error on line 1: invalid XML name: w\xe6"}, + } + for _, test := range tests { + d := NewDecoder(strings.NewReader(test.input)) + d.Strict = false + + var err error + for _, err = d.Token(); err == nil; _, err = d.Token() { + } + if _, ok := err.(*SyntaxError); test.syntaxError && !ok { + t.Fatalf(`xmlInput "%s": expected SyntaxError not received`, test.input) + } + if err.Error() != test.expectedError { + t.Fatalf(`err.Error() = "%s", want "%s"`, err.Error(), test.expectedError) + } + } +} + func TestSyntax(t *testing.T) { for i := range xmlInput { d := NewDecoder(strings.NewReader(xmlInput[i])) @@ -584,6 +691,37 @@ func TestValuelessAttrs(t *testing.T) { } } +const testInputAutoClose = ` + + +` + +var cookedTokensAutoClose = []Token{ + CharData("\n"), + StartElement{Name: Name{Space: "", Local: "lol"}, Attr: []Attr{}}, + EndElement{Name: Name{Space: "", Local: "lol"}}, + CharData("\n"), + StartElement{Name: Name{Space: "", Local: "lol"}, Attr: []Attr{}}, + EndElement{Name: Name{Space: "", Local: "lol"}}, + CharData("\n"), +} + +func TestTokenAutoClose(t *testing.T) { + d := NewDecoder(strings.NewReader(testInputAutoClose)) + d.Strict = false + d.AutoClose = []string{"LOL"} + + for i, want := range cookedTokensAutoClose { + have, err := d.Token() + if err != nil { + t.Fatalf("token %d: unexpected error: %s", i, err) + } + if !reflect.DeepEqual(have, want) { + t.Errorf("token %d = %#v want %#v", i, have, want) + } + } +} + func TestCopyTokenCharData(t *testing.T) { data := []byte("same data") var tok1 Token = CharData(data) @@ -597,6 +735,45 @@ func TestCopyTokenCharData(t *testing.T) { } } +func TestCopyTokenComment(t *testing.T) { + data := []byte("same data") + var tok1 Token = Comment(data) + tok2 := CopyToken(tok1) + if !reflect.DeepEqual(tok1, tok2) { + t.Error("CopyToken(Comment) != Comment") + } + data[1] = 'o' + if reflect.DeepEqual(tok1, tok2) { + t.Error("CopyToken(Comment) uses same buffer.") + } +} + +func TestCopyTokenDirective(t *testing.T) { + data := []byte("same data") + var tok1 Token = Directive(data) + tok2 := CopyToken(tok1) + if !reflect.DeepEqual(tok1, tok2) { + t.Error("CopyToken(Directive) != Directive") + } + data[1] = 'o' + if reflect.DeepEqual(tok1, tok2) { + t.Error("CopyToken(Directive) uses same buffer.") + } +} + +func TestCopyTokenProcInst(t *testing.T) { + data := []byte("same data") + var tok1 Token = ProcInst{"hello", data} + tok2 := CopyToken(tok1) + if !reflect.DeepEqual(tok1, tok2) { + t.Error("CopyToken(ProcInst) != ProcInst") + } + data[1] = 'o' + if reflect.DeepEqual(tok1, tok2) { + t.Error("CopyToken(ProcInst) uses same buffer.") + } +} + func TestCopyTokenStartElement(t *testing.T) { elt := StartElement{Name{"", "hello"}, []Attr{{Name{"", "lang"}, "en"}}} var tok1 Token = elt @@ -613,18 +790,16 @@ func TestCopyTokenStartElement(t *testing.T) { } } -func TestSyntaxErrorLineNum(t *testing.T) { - testInput := "

Foo

\n\n

Bar\n" - d := NewDecoder(strings.NewReader(testInput)) - var err error - for _, err = d.Token(); err == nil; _, err = d.Token() { - } - synerr, ok := err.(*SyntaxError) - if !ok { - t.Error("Expected SyntaxError.") +func TestCopyTokenDefaultCase(t *testing.T) { + data := []byte("same data") + var tok1 = Token(data) + tok2 := CopyToken(data) + if !reflect.DeepEqual(tok1, tok2) { + t.Error("CopyToken(Token]) != Token") } - if synerr.Line != 3 { - t.Error("SyntaxError didn't have correct line number.") + data[1] = 'o' + if !reflect.DeepEqual(tok1, tok2) { + t.Error("CopyToken(CharData) uses different buffer.") } } @@ -677,7 +852,6 @@ var characterTests = []struct { } func TestDisallowedCharacters(t *testing.T) { - for i, tt := range characterTests { d := NewDecoder(strings.NewReader(tt.in)) var err error @@ -778,18 +952,44 @@ func TestEscapeTextIOErrors(t *testing.T) { } } -func TestEscapeTextInvalidChar(t *testing.T) { - input := []byte("A \x00 terminated string.") - expected := "A \uFFFD terminated string." +var escapeTextTests = []struct { + input []byte + expected string +}{ + {[]byte("A \" terminated string."), "A " terminated string."}, + {[]byte(`A ' terminated string.`), "A ' terminated string."}, + {[]byte("A & terminated string."), "A & terminated string."}, + {[]byte("A < terminated string."), "A < terminated string."}, + {[]byte("A > terminated string."), "A > terminated string."}, + {[]byte("A \t terminated string."), "A terminated string."}, + {[]byte("A \n terminated string."), "A terminated string."}, + {[]byte("A \r terminated string."), "A terminated string."}, + {[]byte("A \x00 terminated string."), "A \uFFFD terminated string."}, +} - buff := new(bytes.Buffer) - if err := EscapeText(buff, input); err != nil { - t.Fatalf("have %v, want nil", err) +func TestEscapeText(t *testing.T) { + for _, test := range escapeTextTests { + buff := new(bytes.Buffer) + if err := EscapeText(buff, test.input); err != nil { + t.Fatalf("have %v, want nil", err) + } + + text := buff.String() + if text != test.expected { + t.Errorf("have %v, want %v", text, test.expected) + } } - text := buff.String() +} + +func TestEscape(t *testing.T) { + for _, test := range escapeTextTests { + buff := new(bytes.Buffer) + Escape(buff, test.input) - if text != expected { - t.Errorf("have %v, want %v", text, expected) + text := buff.String() + if text != test.expected { + t.Errorf("have %v, want %v", text, test.expected) + } } }