mirror of
https://github.com/imjasonh/csvstruct
synced 2026-07-07 02:32:20 +00:00
278 lines
5.7 KiB
Go
278 lines
5.7 KiB
Go
package csvstruct
|
|
|
|
import (
|
|
"bytes"
|
|
"net"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// Accounts for changes between Go 1.3 and 1.4 that changed how encoding/csv encodes empty strings
|
|
// See https://github.com/golang/go/commit/6ad2749dcd614270ac58c5254b6ada3bce0af090
|
|
// Set this to true if tests are failing against Go 1.3 or before
|
|
const backcompat = false
|
|
|
|
func TestEncodeNext(t *testing.T) {
|
|
type row struct {
|
|
Foo, Bar, Baz string
|
|
}
|
|
|
|
for _, c := range []struct {
|
|
rows []interface{}
|
|
want string
|
|
}{{
|
|
[]interface{}{row{"a", "b", "c"}, row{"d", "e", "f"}},
|
|
`Foo,Bar,Baz
|
|
a,b,c
|
|
d,e,f
|
|
`,
|
|
}, {
|
|
[]interface{}{row{"a", "", ""}, row{"", "b", ""}},
|
|
`Foo,Bar,Baz
|
|
a,,
|
|
,b,
|
|
`,
|
|
}, {
|
|
// Encoding incomplete structs still fills in missing columns.
|
|
[]interface{}{row{Foo: "a"}, struct{ Foo, Bar string }{Bar: "b"}},
|
|
`Foo,Bar,Baz
|
|
a,,
|
|
,b,
|
|
`,
|
|
}, {
|
|
// Encoding unexported fields.
|
|
[]interface{}{struct{ Exported, unexported string }{"a", "b"}},
|
|
`Exported
|
|
a
|
|
`,
|
|
}, {
|
|
// Encoding renamed and ignored fields.
|
|
[]interface{}{struct {
|
|
Foo string `csv:"renamed_foo"`
|
|
Bar string
|
|
Ignored string `csv:"-"`
|
|
Baz string
|
|
}{"a", "b", "c", "d"}},
|
|
`renamed_foo,Bar,Baz
|
|
a,b,d
|
|
`,
|
|
}, {
|
|
// If the first row contains no encodable fields, further rows
|
|
// will be ignored as well, resulting in an empty output.
|
|
[]interface{}{struct {
|
|
Ignored string `csv:"-"`
|
|
unexported string
|
|
}{"you", "won't"}, struct {
|
|
Exported string
|
|
}{"see"}},
|
|
"",
|
|
}, {
|
|
// Encoding non-string fields.
|
|
[]interface{}{struct {
|
|
Int int
|
|
Int64 int64
|
|
Uint64 uint64
|
|
Float64 float64
|
|
Bool bool
|
|
}{123, -123456789, 123456789, 123.456, true}},
|
|
`Int,Int64,Uint64,Float64,Bool
|
|
123,-123456789,123456789,123.456000,true
|
|
`,
|
|
}, {
|
|
// Encoding rows with different fields.
|
|
// Headers are taken from the fields in the first call to EncodeNext.
|
|
// Further calls add whatever fields they can, and if no fields are
|
|
// shared then the row is not written.
|
|
[]interface{}{
|
|
struct{ Foo, Bar string }{"foo", "bar"},
|
|
struct{ Baz string }{"baz"}, // Will be skipped because it shares no fields.
|
|
struct{ Bar, Baz string }{"bar", "baz"}}, // Only shares Bar, only writes Bar.
|
|
`Foo,Bar
|
|
foo,bar
|
|
,bar
|
|
`,
|
|
}, {
|
|
// Encoding rows with the same fields but with different types.
|
|
[]interface{}{
|
|
struct{ Foo string }{"foo"},
|
|
struct{ Foo int64 }{123},
|
|
struct{ Foo bool }{true}},
|
|
`Foo
|
|
foo
|
|
123
|
|
true
|
|
`,
|
|
}} {
|
|
var buf bytes.Buffer
|
|
e := NewEncoder(&buf)
|
|
for _, r := range c.rows {
|
|
if err := e.EncodeNext(r); err != nil {
|
|
t.Errorf("EncodeNext(%v): %v", r, err)
|
|
}
|
|
}
|
|
got := buf.String()
|
|
if backcompat {
|
|
got = strings.Replace(got, `""`, "", -1)
|
|
}
|
|
if got != c.want {
|
|
t.Errorf("EncodeNext(%v): got %s, want %s", c.rows, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEncode_Opts(t *testing.T) {
|
|
rows := []struct{ A, B, C string }{
|
|
{"a", "b", "c"},
|
|
{"d", "e", "f"}}
|
|
|
|
for _, c := range []struct {
|
|
opts EncodeOpts
|
|
want string
|
|
}{{
|
|
EncodeOpts{Comma: '%'},
|
|
`A%B%C
|
|
a%b%c
|
|
d%e%f
|
|
`,
|
|
}, {
|
|
EncodeOpts{SkipHeader: true},
|
|
`a,b,c
|
|
d,e,f
|
|
`,
|
|
}, {
|
|
EncodeOpts{UseCRLF: true},
|
|
"A,B,C\r\na,b,c\r\nd,e,f\r\n",
|
|
}} {
|
|
var buf bytes.Buffer
|
|
e := NewEncoder(&buf).Opts(c.opts)
|
|
for _, r := range rows {
|
|
if err := e.EncodeNext(r); err != nil {
|
|
t.Errorf("EncodeNext(%v): %v", r, err)
|
|
}
|
|
}
|
|
if got := buf.String(); got != c.want {
|
|
t.Errorf("EncodeNext(%v): got %s, want %s", rows, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEncode_Map(t *testing.T) {
|
|
for _, c := range []struct {
|
|
rows []map[string]interface{}
|
|
want string
|
|
}{{
|
|
[]map[string]interface{}{{
|
|
"foo": "a",
|
|
"bar": true,
|
|
"baz": 1.23,
|
|
"ip": ip,
|
|
}, {
|
|
"foo": "b",
|
|
"bar": false,
|
|
"baz": 4.56,
|
|
"ip": ip,
|
|
}},
|
|
// Keys are sorted before being written to the header
|
|
`bar,baz,foo,ip
|
|
true,1.23,a,128.0.0.1
|
|
false,4.56,b,128.0.0.1
|
|
`,
|
|
}, {
|
|
[]map[string]interface{}{{
|
|
"foo": "a",
|
|
}, {
|
|
"bar": "b",
|
|
}},
|
|
`foo
|
|
a
|
|
`,
|
|
}, {
|
|
[]map[string]interface{}{{
|
|
"foo": "",
|
|
}, {
|
|
"foo": true,
|
|
}},
|
|
`foo
|
|
|
|
true
|
|
`,
|
|
}} {
|
|
var buf bytes.Buffer
|
|
e := NewEncoder(&buf)
|
|
for _, r := range c.rows {
|
|
if err := e.EncodeNext(r); err != nil {
|
|
t.Errorf("EncodeNext(%v): %v", r, err)
|
|
}
|
|
}
|
|
got := buf.String()
|
|
if backcompat {
|
|
got = strings.Replace(got, `""`, "", -1)
|
|
}
|
|
if got != c.want {
|
|
t.Errorf("EncodeNext(%v): got %s, want %s", c.rows, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tests that encoding a struct then encoding a compatible map works as expected.
|
|
func TestEncode_Hybrid(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
e := NewEncoder(&buf)
|
|
s := struct {
|
|
Foo string `csv:"foo"`
|
|
Bar string
|
|
}{"a", "b"}
|
|
if err := e.EncodeNext(s); err != nil {
|
|
t.Errorf("EncodeNext(%v): %v", r, err)
|
|
}
|
|
m := map[string]interface{}{
|
|
"foo": "c",
|
|
"Bar": "d",
|
|
}
|
|
if err := e.EncodeNext(m); err != nil {
|
|
t.Errorf("EncodeNext(%v): %v", r, err)
|
|
}
|
|
want := `foo,Bar
|
|
a,b
|
|
c,d
|
|
`
|
|
if got := buf.String(); got != want {
|
|
t.Errorf("EncodeNext(%v): got %s, want %s", m, got, want)
|
|
}
|
|
}
|
|
|
|
// Tests that values that implement encoding.TextMarshaler are correctly marshaled.
|
|
func TestEncode_TextMarshaler(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
e := NewEncoder(&buf)
|
|
r := struct{ N net.IP }{ip}
|
|
if err := e.EncodeNext(r); err != nil {
|
|
t.Errorf("EncodeNext(%v): %v", r, err)
|
|
}
|
|
want := `N
|
|
128.0.0.1
|
|
`
|
|
if got := buf.String(); got != want {
|
|
t.Errorf("EncodeNext(%v): got %s, want %s", r, got, want)
|
|
}
|
|
}
|
|
|
|
// Tests that structs with pointer fields are encoded correctly.
|
|
func TestEncode_Ptrs(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
e := NewEncoder(&buf)
|
|
bar := "bar"
|
|
s := struct {
|
|
S string
|
|
SP *string
|
|
}{bar, &bar}
|
|
if err := e.EncodeNext(s); err != nil {
|
|
t.Errorf("EncodeNext(%v): %v", r, err)
|
|
}
|
|
want := `S,SP
|
|
bar,bar
|
|
`
|
|
if got := buf.String(); got != want {
|
|
t.Errorf("EncodeNext(%v): got %s, want %s", s, got, want)
|
|
}
|
|
}
|