| package bencode |
| |
| import ( |
| "bytes" |
| "fmt" |
| "reflect" |
| "sort" |
| "strings" |
| "testing" |
| "time" |
| ) |
| |
| func TestDecode(t *testing.T) { |
| type testCase struct { |
| in string |
| val interface{} |
| expect interface{} |
| err bool |
| unorderedFail bool |
| } |
| |
| type dT struct { |
| X string |
| Y int |
| Z string `bencode:"zff"` |
| } |
| |
| type Embedded struct { |
| B string |
| } |
| |
| type issue22 struct { |
| X string `bencode:"x"` |
| Time myTimeType `bencode:"t"` |
| Foo myBoolType `bencode:"f"` |
| Bar myStringType `bencode:"b"` |
| Slice mySliceType `bencode:"s"` |
| Y string `bencode:"y"` |
| } |
| |
| type issue26 struct { |
| X string `bencode:"x"` |
| Foo myBoolTextType `bencode:"f"` |
| Bar myTextStringType `bencode:"b"` |
| Slice myTextSliceType `bencode:"s"` |
| Y string `bencode:"y"` |
| } |
| |
| type issue22WithErrorChild struct { |
| Name string `bencode:"n"` |
| Error errorMarshalType `bencode:"e"` |
| } |
| |
| type issue26WithErrorChild struct { |
| Name string `bencode:"n"` |
| Error errorTextMarshalType `bencode:"e"` |
| } |
| |
| type discardNonFieldDef struct { |
| B string |
| D string |
| } |
| |
| type twoDefsForSameKey struct { |
| A string |
| A2 string `bencode:"A"` |
| A3 string `bencode:"A"` |
| } |
| |
| now := time.Now() |
| |
| var decodeCases = []testCase{ |
| // integers |
| {`i5e`, new(int), int(5), false, false}, |
| {`i-10e`, new(int), int(-10), false, false}, |
| {`i8e`, new(uint), uint(8), false, false}, |
| {`i8e`, new(uint8), uint8(8), false, false}, |
| {`i8e`, new(uint16), uint16(8), false, false}, |
| {`i8e`, new(uint32), uint32(8), false, false}, |
| {`i8e`, new(uint64), uint64(8), false, false}, |
| {`i8e`, new(int), int(8), false, false}, |
| {`i8e`, new(int8), int8(8), false, false}, |
| {`i8e`, new(int16), int16(8), false, false}, |
| {`i8e`, new(int32), int32(8), false, false}, |
| {`i8e`, new(int64), int64(8), false, false}, |
| {`i0e`, new(*int), new(int), false, false}, |
| {`i-2e`, new(uint), nil, true, false}, |
| |
| // bools |
| {`i1e`, new(bool), true, false, false}, |
| {`i0e`, new(bool), false, false, false}, |
| {`i0e`, new(*bool), new(bool), false, false}, |
| {`i8e`, new(bool), true, false, false}, |
| |
| // strings |
| {`3:foo`, new(string), "foo", false, false}, |
| {`4:foob`, new(string), "foob", false, false}, |
| {`0:`, new(*string), new(string), false, false}, |
| {`6:short`, new(string), nil, true, false}, |
| |
| // lists |
| {`l3:foo3:bare`, new([]string), []string{"foo", "bar"}, false, false}, |
| {`li15ei20ee`, new([]int), []int{15, 20}, false, false}, |
| {`ld3:fooi0eed3:bari1eee`, new([]map[string]int), []map[string]int{ |
| {"foo": 0}, |
| {"bar": 1}, |
| }, false, false}, |
| |
| // dicts |
| |
| {`d3:foo3:bar4:foob3:fooe`, new(map[string]string), map[string]string{ |
| "foo": "bar", |
| "foob": "foo", |
| }, false, false}, |
| {`d1:X3:foo1:Yi10e3:zff3:bare`, new(dT), dT{"foo", 10, "bar"}, false, false}, |
| |
| // encoding/json takes, if set, the tag as name and doesn't falls back to the |
| // struct field's name. |
| {`d1:X3:foo1:Yi10e1:Z3:bare`, new(dT), dT{"foo", 10, ""}, false, false}, |
| |
| {`d1:X3:foo1:Yi10e1:h3:bare`, new(dT), dT{"foo", 10, ""}, false, false}, |
| {`d3:fooli0ei1ee3:barli2ei3eee`, new(map[string][]int), map[string][]int{ |
| "foo": []int{0, 1}, |
| "bar": []int{2, 3}, |
| }, false, false}, |
| {`de`, new(map[string]string), map[string]string{}, false, false}, |
| |
| // into interfaces |
| {`i5e`, new(interface{}), int64(5), false, false}, |
| {`li5ee`, new(interface{}), []interface{}{int64(5)}, false, false}, |
| {`5:hello`, new(interface{}), "hello", false, false}, |
| {`d5:helloi5ee`, new(interface{}), map[string]interface{}{"hello": int64(5)}, false, false}, |
| |
| // into values whose type support the Unmarshaler interface |
| {`1:y`, new(myTimeType), nil, true, false}, |
| {fmt.Sprintf("i%de", now.Unix()), new(myTimeType), myTimeType{time.Unix(now.Unix(), 0)}, false, false}, |
| {`1:y`, new(myBoolType), myBoolType(true), false, false}, |
| {`i42e`, new(myBoolType), nil, true, false}, |
| {`1:n`, new(myBoolType), myBoolType(false), false, false}, |
| {`1:n`, new(errorMarshalType), nil, true, false}, |
| {`li102ei111ei111ee`, new(myStringType), myStringType("foo"), false, false}, |
| {`i42e`, new(myStringType), nil, true, false}, |
| {`d1:ai1e3:foo3:bare`, new(mySliceType), mySliceType{"a", int64(1), "foo", "bar"}, false, false}, |
| {`i42e`, new(mySliceType), nil, true, false}, |
| |
| // into values who have a child which type supports the Unmarshaler interface |
| { |
| fmt.Sprintf(`d1:b3:foo1:f1:y1:sd1:f3:foo1:ai42ee1:ti%de1:x1:x1:y1:ye`, now.Unix()), |
| new(issue22), |
| issue22{ |
| X: "x", |
| Time: myTimeType{time.Unix(now.Unix(), 0)}, |
| Foo: myBoolType(true), |
| Bar: myStringType("foo"), |
| Slice: mySliceType{"a", int64(42), "f", "foo"}, |
| Y: "y", |
| }, |
| false, |
| false, |
| }, |
| { |
| `d1:ei42e1:n3:fooe`, |
| new(issue22WithErrorChild), |
| nil, |
| true, |
| false, |
| }, |
| |
| // into values whose type support the TextUnmarshaler interface |
| {`1:y`, new(myBoolTextType), myBoolTextType(true), false, false}, |
| {`1:n`, new(myBoolTextType), myBoolTextType(false), false, false}, |
| {`i42e`, new(myBoolTextType), nil, true, false}, |
| {`1:n`, new(errorTextMarshalType), nil, true, false}, |
| {`7:foo_bar`, new(myTextStringType), myTextStringType("bar"), false, false}, |
| {`i42e`, new(myTextStringType), nil, true, false}, |
| {`7:a,b,c,d`, new(myTextSliceType), myTextSliceType{"a", "b", "c", "d"}, false, false}, |
| {`i42e`, new(myTextSliceType), nil, true, false}, |
| |
| // into values who have a child which type supports the TextUnmarshaler interface |
| { |
| `d1:b7:foo_bar1:f1:y1:s5:1,2,31:x1:x1:y1:ye`, |
| new(issue26), |
| issue26{ |
| X: "x", |
| Foo: myBoolTextType(true), |
| Bar: myTextStringType("bar"), |
| Slice: myTextSliceType{"1", "2", "3"}, |
| Y: "y", |
| }, |
| false, |
| false, |
| }, |
| { |
| `d1:ei42e1:n3:fooe`, |
| new(issue26WithErrorChild), |
| nil, |
| true, |
| false, |
| }, |
| |
| // malformed |
| {`i53:foo`, new(interface{}), nil, true, false}, |
| {`6:foo`, new(interface{}), nil, true, false}, |
| {`di5ei2ee`, new(interface{}), nil, true, false}, |
| {`d3:fooe`, new(interface{}), nil, true, false}, |
| {`l3:foo3:bar`, new(interface{}), nil, true, false}, |
| {`d-1:`, new(interface{}), nil, true, false}, |
| |
| // embedded structs |
| {`d1:A3:foo1:B3:bare`, new(struct { |
| A string |
| Embedded |
| }), struct { |
| A string |
| Embedded |
| }{"foo", Embedded{"bar"}}, false, false}, |
| |
| // Embedded structs with a valid tag are encoded as a definition |
| {`d1:B3:bar6:nestedd1:B3:fooee`, new(struct { |
| Embedded `bencode:"nested"` |
| }), struct { |
| Embedded `bencode:"nested"` |
| }{Embedded{"foo"}}, false, false}, |
| |
| // Don't fail when reading keys missing from the struct |
| {"d1:A7:discard1:B4:take1:C7:discard1:D4:takee", |
| new(discardNonFieldDef), |
| discardNonFieldDef{"take", "take"}, |
| false, |
| false, |
| }, |
| |
| // Don't fail when reading the same key twice |
| {"d1:A1:a1:A1:b1:A1:c1:A1:de", new(twoDefsForSameKey), |
| twoDefsForSameKey{"", "", "d"}, false, false}, |
| |
| // Empty struct |
| {"de", new(struct{}), struct{}{}, false, false}, |
| |
| // Fail on unordered dictionaries |
| {"d1:Yi10e1:X1:a3:zff1:ce", new(dT), dT{}, true, true}, |
| {"d3:zff1:c1:Yi10e1:X1:ae", new(dT), dT{}, true, true}, |
| } |
| |
| for i, tt := range decodeCases { |
| dec := NewDecoder(strings.NewReader(tt.in)) |
| dec.SetFailOnUnorderedKeys(tt.unorderedFail) |
| err := dec.Decode(tt.val) |
| if !tt.err && err != nil { |
| t.Errorf("#%d (%v): Unexpected err: %v", i, tt.in, err) |
| continue |
| } |
| if tt.err && err == nil { |
| t.Errorf("#%d (%v): Expected err is nil", i, tt.in) |
| continue |
| } |
| v := reflect.ValueOf(tt.val).Elem().Interface() |
| if !reflect.DeepEqual(v, tt.expect) && !tt.err { |
| t.Errorf("#%d (%v): Val: %#v != %#v", i, tt.in, v, tt.expect) |
| } |
| } |
| } |
| |
| func TestRawDecode(t *testing.T) { |
| type testCase struct { |
| in string |
| expect []byte |
| err bool |
| } |
| |
| var rawDecodeCases = []testCase{ |
| {`i5e`, []byte(`i5e`), false}, |
| {`5:hello`, []byte(`5:hello`), false}, |
| {`li5ei10e5:helloe`, []byte(`li5ei10e5:helloe`), false}, |
| {`llleee`, []byte(`llleee`), false}, |
| {`li5eli5eli5eeee`, []byte(`li5eli5eli5eeee`), false}, |
| {`d5:helloi5ee`, []byte(`d5:helloi5ee`), false}, |
| } |
| |
| for i, tt := range rawDecodeCases { |
| var x RawMessage |
| err := DecodeString(tt.in, &x) |
| if !tt.err && err != nil { |
| t.Errorf("#%d: Unexpected err: %v", i, err) |
| continue |
| } |
| if tt.err && err == nil { |
| t.Errorf("#%d: Expected err is nil", i) |
| continue |
| } |
| if !reflect.DeepEqual(x, RawMessage(tt.expect)) && !tt.err { |
| t.Errorf("#%d: Val: %#v != %#v", i, x, tt.expect) |
| } |
| } |
| } |
| |
| type myStringType string |
| |
| // UnmarshalBencode implements Unmarshaler.UnmarshalBencode |
| func (mst *myStringType) UnmarshalBencode(b []byte) error { |
| var raw []byte |
| err := DecodeBytes(b, &raw) |
| if err != nil { |
| return err |
| } |
| |
| *mst = myStringType(raw) |
| return nil |
| } |
| |
| type mySliceType []interface{} |
| |
| // UnmarshalBencode implements Unmarshaler.UnmarshalBencode |
| func (mst *mySliceType) UnmarshalBencode(b []byte) error { |
| m := make(map[string]interface{}) |
| err := DecodeBytes(b, &m) |
| if err != nil { |
| return err |
| } |
| |
| keys := make([]string, 0, len(m)) |
| for k := range m { |
| keys = append(keys, k) |
| } |
| sort.Strings(keys) |
| |
| raw := make([]interface{}, 0, len(m)*2) |
| for _, key := range keys { |
| raw = append(raw, key, m[key]) |
| } |
| |
| *mst = mySliceType(raw) |
| return nil |
| } |
| |
| type myTextStringType string |
| |
| // UnmarshalText implements TextUnmarshaler.UnmarshalText |
| func (mst *myTextStringType) UnmarshalText(b []byte) error { |
| *mst = myTextStringType(bytes.TrimPrefix(b, []byte("foo_"))) |
| return nil |
| } |
| |
| type myTextSliceType []string |
| |
| // UnmarshalText implements TextUnmarshaler.UnmarshalText |
| func (mst *myTextSliceType) UnmarshalText(b []byte) error { |
| raw := string(b) |
| *mst = strings.Split(raw, ",") |
| return nil |
| } |
| |
| func TestNestedRawDecode(t *testing.T) { |
| type testCase struct { |
| in string |
| val interface{} |
| expect interface{} |
| err bool |
| } |
| |
| type message struct { |
| Key string |
| Val int |
| Raw RawMessage |
| } |
| |
| var cases = []testCase{ |
| {`li5e5:hellod1:a1:beli5eee`, new([]RawMessage), []RawMessage{ |
| RawMessage(`i5e`), |
| RawMessage(`5:hello`), |
| RawMessage(`d1:a1:be`), |
| RawMessage(`li5ee`), |
| }, false}, |
| {`d1:a1:b1:c1:de`, new(map[string]RawMessage), map[string]RawMessage{ |
| "a": RawMessage(`1:b`), |
| "c": RawMessage(`1:d`), |
| }, false}, |
| {`d3:Key5:hello3:Rawldedei5e1:ae3:Vali10ee`, new(message), message{ |
| Key: "hello", |
| Val: 10, |
| Raw: RawMessage(`ldedei5e1:ae`), |
| }, false}, |
| } |
| |
| for i, tt := range cases { |
| err := DecodeString(tt.in, tt.val) |
| if !tt.err && err != nil { |
| t.Errorf("#%d: Unexpected err: %v", i, err) |
| continue |
| } |
| if tt.err && err == nil { |
| t.Errorf("#%d: Expected err is nil", i) |
| continue |
| } |
| v := reflect.ValueOf(tt.val).Elem().Interface() |
| if !reflect.DeepEqual(v, tt.expect) && !tt.err { |
| t.Errorf("#%d: Val:\n%#v !=\n%#v", i, v, tt.expect) |
| } |
| } |
| } |