1
2
3
4
5
6
7
8
9
10
11
12
13 package kivik
14
15 import (
16 "context"
17 "encoding/json"
18 "errors"
19 "io"
20 "net/http"
21 "strconv"
22 "strings"
23 "testing"
24 "time"
25
26 "github.com/google/go-cmp/cmp"
27 "gitlab.com/flimzy/testy"
28
29 "github.com/go-kivik/kivik/v4/driver"
30 "github.com/go-kivik/kivik/v4/int/mock"
31 )
32
33 func TestRowsIteratorNext(t *testing.T) {
34 const expected = "foo error"
35 r := &rowsIterator{
36 Rows: &mock.Rows{
37 NextFunc: func(_ *driver.Row) error { return errors.New(expected) },
38 },
39 }
40 var i driver.Row
41 err := r.Next(&i)
42 if !testy.ErrorMatches(expected, err) {
43 t.Errorf("Unexpected error: %s", err)
44 }
45 }
46
47 func TestNextResultSet(t *testing.T) {
48 t.Run("two resultsets", func(t *testing.T) {
49 r := multiResultSet()
50
51 ids := []string{}
52 for r.NextResultSet() {
53 for r.Next() {
54 id, _ := r.ID()
55 ids = append(ids, id)
56 }
57 }
58 if err := r.Err(); err != nil {
59 t.Error(err)
60 }
61 want := []string{"1", "2", "3", "x", "y"}
62 if d := cmp.Diff(want, ids); d != "" {
63 t.Error(d)
64 }
65 })
66 t.Run("called out of order", func(t *testing.T) {
67 r := multiResultSet()
68
69 if !r.Next() {
70 t.Fatal("expected next to return true")
71 }
72 if r.NextResultSet() {
73 t.Fatal("expected NextResultSet to return false")
74 }
75
76 wantErr := "must call NextResultSet before Next"
77 err := r.Err()
78 if !testy.ErrorMatches(wantErr, err) {
79 t.Errorf("Unexpected error: %s", err)
80 }
81 })
82 t.Run("next only", func(t *testing.T) {
83 r := multiResultSet()
84
85 ids := []string{}
86 for r.Next() {
87 id, _ := r.ID()
88 ids = append(ids, id)
89 }
90 if err := r.Err(); err != nil {
91 t.Error(err)
92 }
93 want := []string{"1", "2", "3", "x", "y"}
94 if d := testy.DiffInterface(want, ids); d != nil {
95 t.Error(d)
96 }
97 })
98 t.Run("don't call NextResultSet in loop", func(t *testing.T) {
99 r := multiResultSet()
100
101 ids := []string{}
102 r.NextResultSet()
103 for r.Next() {
104 id, _ := r.ID()
105 ids = append(ids, id)
106 }
107 _ = r.Next()
108 if err := r.Err(); err != nil {
109 t.Error(err)
110 }
111
112
113
114 want := []string{"1", "2", "3"}
115 if d := testy.DiffInterface(want, ids); d != nil {
116 t.Error(d)
117 }
118 })
119 }
120
121 func multiResultSet() *ResultSet {
122 rows := []interface{}{
123 &driver.Row{ID: "1", Doc: strings.NewReader(`{"foo":"bar"}`)},
124 &driver.Row{ID: "2", Doc: strings.NewReader(`{"foo":"bar"}`)},
125 &driver.Row{ID: "3", Doc: strings.NewReader(`{"foo":"bar"}`)},
126 int64(5),
127 &driver.Row{ID: "x", Doc: strings.NewReader(`{"foo":"bar"}`)},
128 &driver.Row{ID: "y", Doc: strings.NewReader(`{"foo":"bar"}`)},
129 int64(2),
130 }
131 var offset int64
132
133 return newResultSet(context.Background(), nil, &mock.Rows{
134 NextFunc: func(r *driver.Row) error {
135 if len(rows) == 0 {
136 return io.EOF
137 }
138 row := rows[0]
139 rows = rows[1:]
140 switch t := row.(type) {
141 case *driver.Row:
142 *r = *t
143 return nil
144 case int64:
145 offset = t
146 return driver.EOQ
147 default:
148 panic("unknown type")
149 }
150 },
151 OffsetFunc: func() int64 {
152 return offset
153 },
154 })
155 }
156
157 func TestScanAllDocs(t *testing.T) {
158 type tt struct {
159 rows *ResultSet
160 dest interface{}
161 err string
162 }
163
164 tests := testy.NewTable()
165 tests.Add("non-pointer dest", tt{
166 dest: "string",
167 err: "must pass a pointer to ScanAllDocs",
168 })
169 tests.Add("nil pointer dest", tt{
170 dest: (*string)(nil),
171 err: "nil pointer passed to ScanAllDocs",
172 })
173 tests.Add("not a slice or array", tt{
174 dest: &ResultSet{},
175 err: "dest must be a pointer to a slice or array",
176 })
177 tests.Add("0-length array", tt{
178 dest: func() *[0]string { var x [0]string; return &x }(),
179 err: "0-length array passed to ScanAllDocs",
180 })
181 tests.Add("No docs to read", tt{
182 rows: newResultSet(context.Background(), nil, &mock.Rows{}),
183 dest: func() *[]string { return &[]string{} }(),
184 })
185 tests.Add("Success", func() interface{} {
186 rows := []*driver.Row{
187 {Doc: strings.NewReader(`{"foo":"bar"}`)},
188 }
189 return tt{
190 rows: newResultSet(context.Background(), nil, &mock.Rows{
191 NextFunc: func(r *driver.Row) error {
192 if len(rows) == 0 {
193 return io.EOF
194 }
195 *r = *rows[0]
196 rows = rows[1:]
197 return nil
198 },
199 }),
200 dest: func() *[]json.RawMessage { return &[]json.RawMessage{} }(),
201 }
202 })
203 tests.Add("Success, slice of pointers", func() interface{} {
204 rows := []*driver.Row{
205 {Doc: strings.NewReader(`{"foo":"bar"}`)},
206 }
207 return tt{
208 rows: newResultSet(context.Background(), nil, &mock.Rows{
209 NextFunc: func(r *driver.Row) error {
210 if len(rows) == 0 {
211 return io.EOF
212 }
213 *r = *rows[0]
214 rows = rows[1:]
215 return nil
216 },
217 }),
218 dest: func() *[]*json.RawMessage { return &[]*json.RawMessage{} }(),
219 }
220 })
221 tests.Add("Success, long array", func() interface{} {
222 rows := []*driver.Row{
223 {Doc: strings.NewReader(`{"foo":"bar"}`)},
224 }
225 return tt{
226 rows: newResultSet(context.Background(), nil, &mock.Rows{
227 NextFunc: func(r *driver.Row) error {
228 if len(rows) == 0 {
229 return io.EOF
230 }
231 *r = *rows[0]
232 rows = rows[1:]
233 return nil
234 },
235 }),
236 dest: func() *[5]*json.RawMessage { return &[5]*json.RawMessage{} }(),
237 }
238 })
239 tests.Add("Success, short array", func() interface{} {
240 rows := []*driver.Row{
241 {Doc: strings.NewReader(`{"foo":"bar"}`)},
242 {Doc: strings.NewReader(`{"foo":"bar"}`)},
243 {Doc: strings.NewReader(`{"foo":"bar"}`)},
244 }
245 return tt{
246 rows: newResultSet(context.Background(), nil, &mock.Rows{
247 NextFunc: func(r *driver.Row) error {
248 if len(rows) == 0 {
249 return io.EOF
250 }
251 *r = *rows[0]
252 rows = rows[1:]
253 return nil
254 },
255 }),
256 dest: func() *[1]*json.RawMessage { return &[1]*json.RawMessage{} }(),
257 }
258 })
259 tests.Run(t, func(t *testing.T, tt tt) {
260 if tt.rows == nil {
261 tt.rows = newResultSet(context.Background(), nil, &mock.Rows{})
262 }
263 err := ScanAllDocs(tt.rows, tt.dest)
264 if !testy.ErrorMatches(tt.err, err) {
265 t.Errorf("Unexpected error: %s", err)
266 }
267 if d := testy.DiffAsJSON(testy.Snapshot(t), tt.dest); d != nil {
268 t.Error(d)
269 }
270 })
271 }
272
273 func TestResultSet_Next_resets_iterator_value(t *testing.T) {
274 idx := 0
275 rows := newResultSet(context.Background(), nil, &mock.Rows{
276 NextFunc: func(r *driver.Row) error {
277 idx++
278 switch idx {
279 case 1:
280 r.ID = strconv.Itoa(idx)
281 return nil
282 case 2:
283 return nil
284 }
285 return io.EOF
286 },
287 })
288
289 wantIDs := []string{"1", ""}
290 gotIDs := []string{}
291 for rows.Next() {
292 id, err := rows.ID()
293 if err != nil {
294 t.Fatal(err)
295 }
296 gotIDs = append(gotIDs, id)
297 }
298 if d := cmp.Diff(wantIDs, gotIDs); d != "" {
299 t.Error(d)
300 }
301 }
302
303 func TestResultSet_Getters(t *testing.T) {
304 const id = "foo"
305 key := []byte("[1234]")
306 const offset = int64(2)
307 const totalrows = int64(3)
308 const updateseq = "asdfasdf"
309 r := &ResultSet{
310 iter: &iter{
311 state: stateRowReady,
312 curVal: &driver.Row{
313 ID: id,
314 Key: key,
315 },
316 },
317 rowsi: &mock.Rows{
318 OffsetFunc: func() int64 { return offset },
319 TotalRowsFunc: func() int64 { return totalrows },
320 UpdateSeqFunc: func() string { return updateseq },
321 },
322 }
323
324 t.Run("ID", func(t *testing.T) {
325 result, _ := r.ID()
326 if id != result {
327 t.Errorf("Unexpected result: %v", result)
328 }
329 })
330
331 t.Run("Key", func(t *testing.T) {
332 result, _ := r.Key()
333 if string(key) != result {
334 t.Errorf("Unexpected result: %v", result)
335 }
336 })
337
338 t.Run("Not Ready", func(t *testing.T) {
339 t.Run("ID", func(t *testing.T) {
340 rowsi := &mock.Rows{
341 NextFunc: func(r *driver.Row) error {
342 r.ID = id
343 return nil
344 },
345 }
346 r := newResultSet(context.Background(), nil, rowsi)
347
348 _, err := r.ID()
349 if !testy.ErrorMatches("kivik: Iterator access before calling Next", err) {
350 t.Errorf("Unexpected error: %v", err)
351 }
352 })
353
354 t.Run("Key", func(t *testing.T) {
355 rowsi := &mock.Rows{
356 NextFunc: func(r *driver.Row) error {
357 r.Key = key
358 return nil
359 },
360 }
361 r := newResultSet(context.Background(), nil, rowsi)
362
363 _, err := r.Key()
364 if !testy.ErrorMatches("kivik: Iterator access before calling Next", err) {
365 t.Errorf("Unexpected error: %v", err)
366 }
367 })
368 })
369 }
370
371 func TestResultSet_Metadata(t *testing.T) {
372 t.Run("iteration incomplete", func(t *testing.T) {
373 r := newResultSet(context.Background(), nil, &mock.Rows{
374 OffsetFunc: func() int64 { return 123 },
375 TotalRowsFunc: func() int64 { return 234 },
376 UpdateSeqFunc: func() string { return "seq" },
377 })
378 _, err := r.Metadata()
379 wantErr := "Metadata must not be called until result set iteration is complete"
380 if !testy.ErrorMatches(wantErr, err) {
381 t.Errorf("Unexpected error: %s", err)
382 }
383 })
384
385 check := func(t *testing.T, r *ResultSet) {
386 t.Helper()
387 for r.Next() {
388 }
389 meta, err := r.Metadata()
390 if err != nil {
391 t.Fatal(err)
392 }
393 if d := testy.DiffInterface(testy.Snapshot(t), meta); d != nil {
394 t.Error(d)
395 }
396 }
397
398 t.Run("Standard", func(t *testing.T) {
399 r := newResultSet(context.Background(), nil, &mock.Rows{
400 OffsetFunc: func() int64 { return 123 },
401 TotalRowsFunc: func() int64 { return 234 },
402 UpdateSeqFunc: func() string { return "seq" },
403 })
404 check(t, r)
405 })
406 t.Run("Bookmarker", func(t *testing.T) {
407 expected := "test bookmark"
408 r := newResultSet(context.Background(), nil, &mock.Bookmarker{
409 BookmarkFunc: func() string { return expected },
410 })
411 check(t, r)
412 })
413 t.Run("Warner", func(t *testing.T) {
414 const expected = "test warning"
415 r := newResultSet(context.Background(), nil, &mock.RowsWarner{
416 WarningFunc: func() string { return expected },
417 })
418 check(t, r)
419 })
420 t.Run("query in progress", func(t *testing.T) {
421 rows := []interface{}{
422 &driver.Row{Doc: strings.NewReader(`{"foo":"bar"}`)},
423 &driver.Row{Doc: strings.NewReader(`{"foo":"bar"}`)},
424 &driver.Row{Doc: strings.NewReader(`{"foo":"bar"}`)},
425 }
426
427 r := newResultSet(context.Background(), nil, &mock.Rows{
428 NextFunc: func(r *driver.Row) error {
429 if len(rows) == 0 {
430 return io.EOF
431 }
432 if dr, ok := rows[0].(*driver.Row); ok {
433 rows = rows[1:]
434 *r = *dr
435 return nil
436 }
437 return driver.EOQ
438 },
439 OffsetFunc: func() int64 {
440 return 5
441 },
442 })
443 var i int
444 for r.Next() {
445 i++
446 if i > 10 {
447 panic(i)
448 }
449 }
450 check(t, r)
451 })
452 t.Run("no query in progress", func(t *testing.T) {
453 rows := []interface{}{
454 &driver.Row{Doc: strings.NewReader(`{"foo":"bar"}`)},
455 &driver.Row{Doc: strings.NewReader(`{"foo":"bar"}`)},
456 &driver.Row{Doc: strings.NewReader(`{"foo":"bar"}`)},
457 }
458
459 r := newResultSet(context.Background(), nil, &mock.Rows{
460 NextFunc: func(r *driver.Row) error {
461 if len(rows) == 0 {
462 return io.EOF
463 }
464 if dr, ok := rows[0].(*driver.Row); ok {
465 rows = rows[1:]
466 *r = *dr
467 return nil
468 }
469 return driver.EOQ
470 },
471 OffsetFunc: func() int64 {
472 return 5
473 },
474 })
475 check(t, r)
476 })
477 t.Run("followed by other query in resultset mode", func(t *testing.T) {
478 r := multiResultSet()
479
480 _ = r.NextResultSet()
481 check(t, r)
482 ids := []string{}
483 for r.Next() {
484 id, _ := r.ID()
485 ids = append(ids, id)
486 }
487 want := []string{"x", "y"}
488 if d := testy.DiffInterface(want, ids); d != nil {
489 t.Error(d)
490 }
491 t.Run("second query", func(t *testing.T) {
492 check(t, r)
493 })
494 })
495 t.Run("followed by other query in row mode", func(t *testing.T) {
496 r := multiResultSet()
497
498 check(t, r)
499 ids := []string{}
500 for r.Next() {
501 id, _ := r.ID()
502 ids = append(ids, id)
503 }
504 want := []string{}
505 if d := testy.DiffInterface(want, ids); d != nil {
506 t.Error(d)
507 }
508 t.Run("second query", func(t *testing.T) {
509 check(t, r)
510 })
511 })
512 }
513
514 func Test_bug576(t *testing.T) {
515 rows := newResultSet(context.Background(), nil, &mock.Rows{
516 NextFunc: func(*driver.Row) error {
517 return io.EOF
518 },
519 })
520
521 var result interface{}
522 err := rows.ScanDoc(&result)
523 const wantErr = "kivik: Iterator access before calling Next"
524 wantStatus := http.StatusBadRequest
525 if !testy.ErrorMatches(wantErr, err) {
526 t.Errorf("unexpected error: %s", err)
527 }
528 if status := HTTPStatus(err); status != wantStatus {
529 t.Errorf("Unexpected error status: %v", status)
530 }
531 }
532
533 func TestResultSet_Close_blocks(t *testing.T) {
534 t.Parallel()
535
536 const delay = 100 * time.Millisecond
537
538 type tt struct {
539 rows driver.Rows
540 work func(*ResultSet)
541 }
542
543 tests := testy.NewTable()
544 tests.Add("ScanDoc", tt{
545 rows: &mock.Rows{
546 NextFunc: func(row *driver.Row) error {
547 row.Doc = io.MultiReader(
548 testy.DelayReader(delay),
549 strings.NewReader(`{}`),
550 )
551 return nil
552 },
553 },
554 work: func(rs *ResultSet) {
555 var i interface{}
556 rs.Next()
557 _ = rs.ScanDoc(&i)
558 },
559 })
560 tests.Add("ScanValue", tt{
561 rows: &mock.Rows{
562 NextFunc: func(row *driver.Row) error {
563 row.Value = io.MultiReader(
564 testy.DelayReader(delay),
565 strings.NewReader(`{}`),
566 )
567 return nil
568 },
569 },
570 work: func(rs *ResultSet) {
571 var i interface{}
572 rs.Next()
573 _ = rs.ScanValue(&i)
574 },
575 })
576 tests.Add("Attachments", tt{
577 rows: &mock.Rows{
578 NextFunc: func(row *driver.Row) error {
579 row.Attachments = &mock.Attachments{
580 NextFunc: func(*driver.Attachment) error {
581 time.Sleep(delay)
582 return io.EOF
583 },
584 }
585 return nil
586 },
587 },
588 work: func(rs *ResultSet) {
589 rs.Next()
590 atts, err := rs.Attachments()
591 if err != nil {
592 t.Fatal(err)
593 }
594 for {
595 _, _ = atts.Next()
596 }
597 },
598 })
599
600 tests.Run(t, func(t *testing.T, tt tt) {
601 t.Parallel()
602
603 rs := newResultSet(context.Background(), nil, tt.rows)
604
605 start := time.Now()
606 go tt.work(rs)
607 time.Sleep(delay / 2)
608 _ = rs.Close()
609 if elapsed := time.Since(start); elapsed < delay {
610 t.Errorf("rs.Close() didn't block long enough (%v < %v)", elapsed, delay)
611 }
612 })
613 }
614
View as plain text