github.com/go-kivik/kivik/v4@v4.3.2/resultset_test.go (about) 1 // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 // use this file except in compliance with the License. You may obtain a copy of 3 // the License at 4 // 5 // http://www.apache.org/licenses/LICENSE-2.0 6 // 7 // Unless required by applicable law or agreed to in writing, software 8 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 // License for the specific language governing permissions and limitations under 11 // the License. 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() // once more to ensure it doesn't error past the end of the first RS 108 if err := r.Err(); err != nil { 109 t.Error(err) 110 } 111 112 // Only the first result set is processed, since NextResultSet is never 113 // called a second time. 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() { //nolint:revive // Consume all rows 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 }