blob: d19b43f21594269c6f462d2092736a4f56142959 [file] [log] [blame]
Ivan Pilat4ae55812015-07-14 22:02:59 -07001// Copyright 2015 The Vanadium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package writer_test
6
7import (
8 "bytes"
9 "encoding/json"
10 "testing"
11 "time"
12
13 "v.io/syncbase/v23/syncbase/nosql"
14 db "v.io/syncbase/x/ref/syncbase/sb51/internal/demodb"
15 "v.io/syncbase/x/ref/syncbase/sb51/internal/writer"
16 "v.io/v23/vdl"
17)
18
19type fakeResultStream struct {
20 rows [][]*vdl.Value
21 curr int
22}
23
24func newResultStream(iRows [][]interface{}) nosql.ResultStream {
25 vRows := make([][]*vdl.Value, len(iRows))
26 for i, iRow := range iRows {
27 vRow := make([]*vdl.Value, len(iRow))
28 for j, iCol := range iRow {
29 vRow[j] = vdl.ValueOf(iCol)
30 }
31 vRows[i] = vRow
32 }
33 return &fakeResultStream{
34 rows: vRows,
35 curr: -1,
36 }
37}
38
39func (f *fakeResultStream) Advance() bool {
40 f.curr++
41 return f.curr < len(f.rows)
42}
43
44func (f *fakeResultStream) Result() []*vdl.Value {
45 if f.curr == -1 {
46 panic("call advance first")
47 }
48 return f.rows[f.curr]
49}
50
51func (f *fakeResultStream) Err() error {
52 return nil
53}
54
55func (f *fakeResultStream) Cancel() {
56 // Nothing to do.
57}
58
59func TestWriteTable(t *testing.T) {
60 type testCase struct {
61 columns []string
62 rows [][]interface{}
63 // To make the test cases easier to read, output should have a leading
64 // newline.
65 output string
66 }
67 tests := []testCase{
68 {
69 []string{"c1", "c2"},
70 [][]interface{}{
71 {5, "foo"},
72 {6, "bar"},
73 },
74 `
75+----+-----+
76| c1 | c2 |
77+----+-----+
78| 5 | foo |
79| 6 | bar |
80+----+-----+
81`,
82 },
83 {
84 []string{"c1", "c2"},
85 [][]interface{}{
86 {500, "foo"},
87 {6, "barbaz"},
88 },
89 `
90+-----+--------+
91| c1 | c2 |
92+-----+--------+
93| 500 | foo |
94| 6 | barbaz |
95+-----+--------+
96`,
97 },
98 {
99 []string{"c1", "reallylongcolumnheader"},
100 [][]interface{}{
101 {5, "foo"},
102 {6, "bar"},
103 },
104 `
105+----+------------------------+
106| c1 | reallylongcolumnheader |
107+----+------------------------+
108| 5 | foo |
109| 6 | bar |
110+----+------------------------+
111`,
112 },
113 { // Numbers.
114 []string{"byte", "uint16", "uint32", "uint64", "int16", "int32", "int64",
115 "float32", "float64", "complex64", "complex128"},
116 [][]interface{}{
117 {
118 byte(12), uint16(1234), uint32(5678), uint64(999888777666), int16(9876), int32(876543), int64(128),
119 float32(3.14159), float64(2.71828182846), complex64(123.0 + 7.0i), complex128(456.789 + 10.1112i),
120 },
121 {
122 byte(9), uint16(99), uint32(999), uint64(9999999), int16(9), int32(99), int64(88),
123 float32(1.41421356237), float64(1.73205080757), complex64(9.87 + 7.65i), complex128(4.32 + 1.0i),
124 },
125 },
126 `
127+------+--------+--------+--------------+-------+--------+-------+--------------------+---------------+--------------------------------------+------------------+
128| byte | uint16 | uint32 | uint64 | int16 | int32 | int64 | float32 | float64 | complex64 | complex128 |
129+------+--------+--------+--------------+-------+--------+-------+--------------------+---------------+--------------------------------------+------------------+
130| 12 | 1234 | 5678 | 999888777666 | 9876 | 876543 | 128 | 3.141590118408203 | 2.71828182846 | 123+7i | 456.789+10.1112i |
131| 9 | 99 | 999 | 9999999 | 9 | 99 | 88 | 1.4142135381698608 | 1.73205080757 | 9.869999885559082+7.650000095367432i | 4.32+1i |
132+------+--------+--------+--------------+-------+--------+-------+--------------------+---------------+--------------------------------------+------------------+
133`,
134 },
135 { // Strings with whitespace should be printed literally.
136 []string{"c1", "c2"},
137 [][]interface{}{
138 {"foo\tbar", "foo\nbar"},
139 },
140 `
141+---------+---------+
142| c1 | c2 |
143+---------+---------+
144| foo bar | foo
145bar |
146+---------+---------+
147`,
148 },
149 { // nil is shown as blank.
150 []string{"c1"},
151 [][]interface{}{
152 {nil},
153 },
154 `
155+----+
156| c1 |
157+----+
158| |
159+----+
160`,
161 },
162 {
163 []string{"c1"},
164 [][]interface{}{
165 {db.Customer{"John Smith", 1, true, db.AddressInfo{"1 Main St.", "Palo Alto", "CA", "94303"}, db.CreditReport{Agency: db.CreditAgencyEquifax, Report: db.AgencyReportEquifaxReport{db.EquifaxCreditReport{'A'}}}}},
166 {db.Invoice{1, 1000, 42, db.AddressInfo{"1 Main St.", "Palo Alto", "CA", "94303"}}},
167 },
168 `
169+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
170| c1 |
171+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
172| {Name: "John Smith", Id: 1, Active: true, Address: {Street: "1 Main St.", City: "Palo Alto", State: "CA", Zip: "94303"}, Credit: {Agency: Equifax, Report: EquifaxReport: {Rating: 65}}} |
173| {CustId: 1, InvoiceNum: 1000, Amount: 42, ShipTo: {Street: "1 Main St.", City: "Palo Alto", State: "CA", Zip: "94303"}} |
174+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
175`,
176 },
177 {
178 []string{"c1"},
179 [][]interface{}{
180 {db.Composite{db.Array2String{"foo", "棎鶊鵱"}, []int32{1, 2}, map[int32]struct{}{1: struct{}{}, 2: struct{}{}}, map[string]int32{"foo": 1, "bar": 2}}},
181 },
182 `
183+----------------------------------------------------------------------------------+
184| c1 |
185+----------------------------------------------------------------------------------+
186| {Arr: ["foo", "棎鶊鵱"], ListInt: [1, 2], MySet: {1, 2}, Map: {"bar": 2, "foo": 1}} |
187+----------------------------------------------------------------------------------+
188`,
189 },
190 { // Types not built in to Go.
191 []string{"time", "type", "union", "enum", "set"},
192 [][]interface{}{
193 {time.Unix(13377331, 0), vdl.TypeOf(map[float32]struct{ B bool }{}), db.TitleOrValueTypeTitle{"dahar master"}, db.ExperianRatingBad, map[int32]struct{}{47: struct{}{}}},
194 },
195 `
196+-------------------------------+----------------------------------------+-----------------------+------+------+
197| time | type | union | enum | set |
198+-------------------------------+----------------------------------------+-----------------------+------+------+
199| 1970-06-04 19:55:31 +0000 UTC | typeobject(map[float32]struct{B bool}) | Title: "dahar master" | Bad | {47} |
200+-------------------------------+----------------------------------------+-----------------------+------+------+
201`,
202 },
203 {
204 []string{"c1"},
205 [][]interface{}{
206 {
207 db.Recursive{nil, &db.Times{time.Unix(123456789, 42244224), time.Duration(13377331)}, map[db.Array2String]db.Recursive{
208 db.Array2String{"a", "b"}: db.Recursive{},
209 db.Array2String{"x\nx", "y\"y"}: db.Recursive{vdl.ValueOf(db.AgencyReportExperianReport{db.ExperianCreditReport{db.ExperianRatingGood}}), nil, nil},
210 }},
211 },
212 },
213 `
214+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
215| c1 |
216+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
217| {Any: nil, Maybe: {Stamp: "1973-11-29 21:33:09.042244224 +0000 UTC", Interval: "13.377331ms"}, Rec: {["a", "b"]: {Any: nil, Maybe: nil, Rec: {}}, ["x\nx", "y\"y"]: {Any: ExperianReport: {Rating: Good}, Maybe: nil, Rec: {}}}} |
218+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
219`,
220 },
221 }
222 for _, test := range tests {
223 var b bytes.Buffer
224 if err := writer.WriteTable(&b, test.columns, newResultStream(test.rows)); err != nil {
225 t.Errorf("Unexpected error: %v", err)
226 continue
227 }
228 // Add a leading newline to the output to match the leading newline
229 // in our test cases.
230 if got, want := "\n"+b.String(), test.output; got != want {
231 t.Errorf("Wrong output:\nGOT:%s\nWANT:%s", got, want)
232 }
233 }
234}
235
236func TestWriteCSV(t *testing.T) {
237 type testCase struct {
238 columns []string
239 rows [][]interface{}
240 delimiter string
241 // To make the test cases easier to read, output should have a leading
242 // newline.
243 output string
244 }
245 tests := []testCase{
246 { // Basic.
247 []string{"c1", "c2"},
248 [][]interface{}{
249 {5, "foo"},
250 {6, "bar"},
251 },
252 ",",
253 `
254c1,c2
2555,foo
2566,bar
257`,
258 },
259 { // Numbers.
260 []string{"byte", "uint16", "uint32", "uint64", "int16", "int32", "int64",
261 "float32", "float64", "complex64", "complex128"},
262 [][]interface{}{
263 {
264 byte(12), uint16(1234), uint32(5678), uint64(999888777666), int16(9876), int32(876543), int64(128),
265 float32(3.14159), float64(2.71828182846), complex64(123.0 + 7.0i), complex128(456.789 + 10.1112i),
266 },
267 {
268 byte(9), uint16(99), uint32(999), uint64(9999999), int16(9), int32(99), int64(88),
269 float32(1.41421356237), float64(1.73205080757), complex64(9.87 + 7.65i), complex128(4.32 + 1.0i),
270 },
271 },
272 ",",
273 `
274byte,uint16,uint32,uint64,int16,int32,int64,float32,float64,complex64,complex128
27512,1234,5678,999888777666,9876,876543,128,3.141590118408203,2.71828182846,123+7i,456.789+10.1112i
2769,99,999,9999999,9,99,88,1.4142135381698608,1.73205080757,9.869999885559082+7.650000095367432i,4.32+1i
277`,
278 },
279 {
280 // Values containing newlines, double quotes, and the delimiter must be
281 // enclosed in double quotes.
282 []string{"c1", "c2"},
283 [][]interface{}{
284 {"foo\tbar", "foo\nbar"},
285 {"foo\"bar\"", "foo,bar"},
286 },
287 ",",
288 `
289c1,c2
290foo bar,"foo
291bar"
292"foo""bar""","foo,bar"
293`,
294 },
295 { // Delimiters other than comma should be supported.
296 []string{"c1", "c2"},
297 [][]interface{}{
298 {"foo\tbar", "foo\nbar"},
299 {"foo\"bar\"", "foo,bar"},
300 },
301 "\t",
302 `
303c1 c2
304"foo bar" "foo
305bar"
306"foo""bar""" foo,bar
307`,
308 },
309 { // Column names should be escaped properly.
310 []string{"foo\tbar", "foo,bar"},
311 [][]interface{}{},
312 ",",
313 `
314foo bar,"foo,bar"
315`,
316 },
317 { // Same as above but use a non-default delimiter.
318 []string{"foo\tbar", "foo,棎鶊鵱"},
319 [][]interface{}{},
320 "\t",
321 `
322"foo bar" foo,棎鶊鵱
323`,
324 },
325 {
326 []string{"c1"},
327 [][]interface{}{
328 {db.Customer{"John Smith", 1, true, db.AddressInfo{"1 Main St.", "Palo Alto", "CA", "94303"}, db.CreditReport{Agency: db.CreditAgencyEquifax, Report: db.AgencyReportEquifaxReport{db.EquifaxCreditReport{'A'}}}}},
329 {db.Invoice{1, 1000, 42, db.AddressInfo{"1 Main St.", "Palo Alto", "CA", "94303"}}},
330 },
331 ",",
332 `
333c1
334"{Name: ""John Smith"", Id: 1, Active: true, Address: {Street: ""1 Main St."", City: ""Palo Alto"", State: ""CA"", Zip: ""94303""}, Credit: {Agency: Equifax, Report: EquifaxReport: {Rating: 65}}}"
335"{CustId: 1, InvoiceNum: 1000, Amount: 42, ShipTo: {Street: ""1 Main St."", City: ""Palo Alto"", State: ""CA"", Zip: ""94303""}}"
336`,
337 },
338 }
339 for _, test := range tests {
340 var b bytes.Buffer
341 if err := writer.WriteCSV(&b, test.columns, newResultStream(test.rows), test.delimiter); err != nil {
342 t.Errorf("Unexpected error: %v", err)
343 continue
344 }
345 // Add a leading newline to the output to match the leading newline
346 // in our test cases.
347 if got, want := "\n"+b.String(), test.output; got != want {
348 t.Errorf("Wrong output:\nGOT: %q\nWANT:%q", got, want)
349 }
350 }
351}
352
353func TestWriteJson(t *testing.T) {
354 type testCase struct {
355 columns []string
356 rows [][]interface{}
357 // To make the test cases easier to read, output should have a leading
358 // newline.
359 output string
360 }
361 tests := []testCase{
362 { // Basic.
363 []string{"c\n1", "c鶊2"},
364 [][]interface{}{
365 {5, "foo\nbar"},
366 {6, "bar\tfoo"},
367 },
368 `
369[{
370 "c\n1": 5,
371 "c鶊2": "foo\nbar"
372}, {
373 "c\n1": 6,
374 "c鶊2": "bar\tfoo"
375}]
376`,
377 },
378 { // Numbers.
379 []string{"byte", "uint16", "uint32", "uint64", "int16", "int32", "int64",
380 "float32", "float64", "complex64", "complex128"},
381 [][]interface{}{
382 {
383 byte(12), uint16(1234), uint32(5678), uint64(999888777666), int16(9876), int32(876543), int64(128),
384 float32(3.14159), float64(2.71828182846), complex64(123.0 + 7.0i), complex128(456.789 + 10.1112i),
385 },
386 {
387 byte(9), uint16(99), uint32(999), uint64(9999999), int16(9), int32(99), int64(88),
388 float32(1.41421356237), float64(1.73205080757), complex64(9.87 + 7.65i), complex128(4.32 + 1.0i),
389 },
390 },
391 `
392[{
393 "byte": 12,
394 "uint16": 1234,
395 "uint32": 5678,
396 "uint64": 999888777666,
397 "int16": 9876,
398 "int32": 876543,
399 "int64": 128,
400 "float32": 3.141590118408203,
401 "float64": 2.71828182846,
402 "complex64": "123+7i",
403 "complex128": "456.789+10.1112i"
404}, {
405 "byte": 9,
406 "uint16": 99,
407 "uint32": 999,
408 "uint64": 9999999,
409 "int16": 9,
410 "int32": 99,
411 "int64": 88,
412 "float32": 1.4142135381698608,
413 "float64": 1.73205080757,
414 "complex64": "9.869999885559082+7.650000095367432i",
415 "complex128": "4.32+1i"
416}]
417`,
418 },
419 { // Empty result.
420 []string{"nothing", "nada", "zilch"},
421 [][]interface{}{},
422 `
423[]
424`,
425 },
426 { // Empty column set.
427 []string{},
428 [][]interface{}{
429 {},
430 {},
431 },
432 `
433[{
434}, {
435}]
436`,
437 },
438 { // Empty values.
439 []string{"blank", "empty", "nil"},
440 [][]interface{}{
441 {struct{}{}, []string{}, nil},
442 },
443 `
444[{
445 "blank": {},
446 "empty": [],
447 "nil": null
448}]
449`,
450 },
451 {
452 []string{"c1"},
453 [][]interface{}{
454 {db.Customer{"John Smith", 1, true, db.AddressInfo{"1 Main St.", "Palo Alto", "CA", "94303"}, db.CreditReport{Agency: db.CreditAgencyEquifax, Report: db.AgencyReportEquifaxReport{db.EquifaxCreditReport{'A'}}}}},
455 {db.Invoice{1, 1000, 42, db.AddressInfo{"1 Main St.", "Palo Alto", "CA", "94303"}}},
456 },
457 `
458[{
459 "c1": {"Name":"John Smith","Id":1,"Active":true,"Address":{"Street":"1 Main St.","City":"Palo Alto","State":"CA","Zip":"94303"},"Credit":{"Agency":"Equifax","Report":{"EquifaxReport":{"Rating":65}}}}
460}, {
461 "c1": {"CustId":1,"InvoiceNum":1000,"Amount":42,"ShipTo":{"Street":"1 Main St.","City":"Palo Alto","State":"CA","Zip":"94303"}}
462}]
463`,
464 },
465 {
466 []string{"nil", "composite", "typeobj"},
467 [][]interface{}{
468 {
469 nil,
470 db.Composite{db.Array2String{"foo", "bar"}, []int32{1, 2}, map[int32]struct{}{1: struct{}{}, 2: struct{}{}}, map[string]int32{"foo": 1, "bar": 2}},
471 vdl.TypeOf(map[string]struct{}{}),
472 },
473 },
474 `
475[{
476 "nil": null,
477 "composite": {"Arr":["foo","bar"],"ListInt":[1,2],"MySet":{"1":true,"2":true},"Map":{"bar":2,"foo":1}},
478 "typeobj": "typeobject(set[string])"
479}]
480`,
481 },
482 {
483 []string{"c1"},
484 [][]interface{}{
485 {
486 db.Recursive{nil, &db.Times{time.Unix(123456789, 42244224), time.Duration(1337)}, map[db.Array2String]db.Recursive{
487 db.Array2String{"a", "棎鶊鵱"}: db.Recursive{},
488 db.Array2String{"x", "y"}: db.Recursive{vdl.ValueOf(db.CreditReport{Agency: db.CreditAgencyExperian, Report: db.AgencyReportExperianReport{db.ExperianCreditReport{db.ExperianRatingGood}}}), nil, nil},
489 }},
490 },
491 },
492 `
493[{
494 "c1": {"Any":null,"Maybe":{"Stamp":"1973-11-29 21:33:09.042244224 +0000 UTC","Interval":"1.337µs"},"Rec":{"[\"a\", \"棎鶊鵱\"]":{"Any":null,"Maybe":null,"Rec":{}},"[\"x\", \"y\"]":{"Any":{"Agency":"Experian","Report":{"ExperianReport":{"Rating":"Good"}}},"Maybe":null,"Rec":{}}}}
495}]
496`,
497 },
498 }
499 for _, test := range tests {
500 var b bytes.Buffer
501 if err := writer.WriteJson(&b, test.columns, newResultStream(test.rows)); err != nil {
502 t.Errorf("Unexpected error: %v", err)
503 continue
504 }
505 var decoded interface{}
506 if err := json.Unmarshal(b.Bytes(), &decoded); err != nil {
507 t.Errorf("Got invalid JSON: %v", err)
508 }
509 // Add a leading newline to the output to match the leading newline
510 // in our test cases.
511 if got, want := "\n"+b.String(), test.output; got != want {
512 t.Errorf("Wrong output:\nGOT: %q\nWANT:%q", got, want)
513 }
514 }
515}