github.com/scraniel/migrate@v0.0.0-20230320185700-339088f36cee/database/postgres/postgres_test.go (about) 1 package postgres 2 3 // error codes https://github.com/lib/pq/blob/master/error.go 4 5 import ( 6 "context" 7 "database/sql" 8 sqldriver "database/sql/driver" 9 "errors" 10 "fmt" 11 "io" 12 "log" 13 "strconv" 14 "strings" 15 "sync" 16 "testing" 17 18 "github.com/golang-migrate/migrate/v4" 19 20 "github.com/dhui/dktest" 21 22 "github.com/golang-migrate/migrate/v4/database" 23 dt "github.com/golang-migrate/migrate/v4/database/testing" 24 "github.com/golang-migrate/migrate/v4/dktesting" 25 _ "github.com/golang-migrate/migrate/v4/source/file" 26 ) 27 28 const ( 29 pgPassword = "postgres" 30 ) 31 32 var ( 33 opts = dktest.Options{ 34 Env: map[string]string{"POSTGRES_PASSWORD": pgPassword}, 35 PortRequired: true, ReadyFunc: isReady} 36 // Supported versions: https://www.postgresql.org/support/versioning/ 37 specs = []dktesting.ContainerSpec{ 38 {ImageName: "postgres:9.5", Options: opts}, 39 {ImageName: "postgres:9.6", Options: opts}, 40 {ImageName: "postgres:10", Options: opts}, 41 {ImageName: "postgres:11", Options: opts}, 42 {ImageName: "postgres:12", Options: opts}, 43 } 44 ) 45 46 func pgConnectionString(host, port string, options ...string) string { 47 options = append(options, "sslmode=disable") 48 return fmt.Sprintf("postgres://postgres:%s@%s:%s/postgres?%s", pgPassword, host, port, strings.Join(options, "&")) 49 } 50 51 func isReady(ctx context.Context, c dktest.ContainerInfo) bool { 52 ip, port, err := c.FirstPort() 53 if err != nil { 54 return false 55 } 56 57 db, err := sql.Open("postgres", pgConnectionString(ip, port)) 58 if err != nil { 59 return false 60 } 61 defer func() { 62 if err := db.Close(); err != nil { 63 log.Println("close error:", err) 64 } 65 }() 66 if err = db.PingContext(ctx); err != nil { 67 switch err { 68 case sqldriver.ErrBadConn, io.EOF: 69 return false 70 default: 71 log.Println(err) 72 } 73 return false 74 } 75 76 return true 77 } 78 79 func mustRun(t *testing.T, d database.Driver, statements []string) { 80 for _, statement := range statements { 81 if err := d.Run(strings.NewReader(statement)); err != nil { 82 t.Fatal(err) 83 } 84 } 85 } 86 87 func Test(t *testing.T) { 88 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 89 ip, port, err := c.FirstPort() 90 if err != nil { 91 t.Fatal(err) 92 } 93 94 addr := pgConnectionString(ip, port) 95 p := &Postgres{} 96 d, err := p.Open(addr) 97 if err != nil { 98 t.Fatal(err) 99 } 100 defer func() { 101 if err := d.Close(); err != nil { 102 t.Error(err) 103 } 104 }() 105 dt.Test(t, d, []byte("SELECT 1")) 106 }) 107 } 108 109 func TestMigrate(t *testing.T) { 110 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 111 ip, port, err := c.FirstPort() 112 if err != nil { 113 t.Fatal(err) 114 } 115 116 addr := pgConnectionString(ip, port) 117 p := &Postgres{} 118 d, err := p.Open(addr) 119 if err != nil { 120 t.Fatal(err) 121 } 122 defer func() { 123 if err := d.Close(); err != nil { 124 t.Error(err) 125 } 126 }() 127 m, err := migrate.NewWithDatabaseInstance("file://./examples/migrations", "postgres", d) 128 if err != nil { 129 t.Fatal(err) 130 } 131 dt.TestMigrate(t, m) 132 }) 133 } 134 135 func TestMultipleStatements(t *testing.T) { 136 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 137 ip, port, err := c.FirstPort() 138 if err != nil { 139 t.Fatal(err) 140 } 141 142 addr := pgConnectionString(ip, port) 143 p := &Postgres{} 144 d, err := p.Open(addr) 145 if err != nil { 146 t.Fatal(err) 147 } 148 defer func() { 149 if err := d.Close(); err != nil { 150 t.Error(err) 151 } 152 }() 153 if err := d.Run(strings.NewReader("CREATE TABLE foo (foo text); CREATE TABLE bar (bar text);")); err != nil { 154 t.Fatalf("expected err to be nil, got %v", err) 155 } 156 157 // make sure second table exists 158 var exists bool 159 if err := d.(*Postgres).conn.QueryRowContext(context.Background(), "SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'bar' AND table_schema = (SELECT current_schema()))").Scan(&exists); err != nil { 160 t.Fatal(err) 161 } 162 if !exists { 163 t.Fatalf("expected table bar to exist") 164 } 165 }) 166 } 167 168 func TestMultipleStatementsInMultiStatementMode(t *testing.T) { 169 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 170 ip, port, err := c.FirstPort() 171 if err != nil { 172 t.Fatal(err) 173 } 174 175 addr := pgConnectionString(ip, port, "x-multi-statement=true") 176 p := &Postgres{} 177 d, err := p.Open(addr) 178 if err != nil { 179 t.Fatal(err) 180 } 181 defer func() { 182 if err := d.Close(); err != nil { 183 t.Error(err) 184 } 185 }() 186 if err := d.Run(strings.NewReader("CREATE TABLE foo (foo text); CREATE INDEX CONCURRENTLY idx_foo ON foo (foo);")); err != nil { 187 t.Fatalf("expected err to be nil, got %v", err) 188 } 189 190 // make sure created index exists 191 var exists bool 192 if err := d.(*Postgres).conn.QueryRowContext(context.Background(), "SELECT EXISTS (SELECT 1 FROM pg_indexes WHERE schemaname = (SELECT current_schema()) AND indexname = 'idx_foo')").Scan(&exists); err != nil { 193 t.Fatal(err) 194 } 195 if !exists { 196 t.Fatalf("expected table bar to exist") 197 } 198 }) 199 } 200 201 func TestErrorParsing(t *testing.T) { 202 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 203 ip, port, err := c.FirstPort() 204 if err != nil { 205 t.Fatal(err) 206 } 207 208 addr := pgConnectionString(ip, port) 209 p := &Postgres{} 210 d, err := p.Open(addr) 211 if err != nil { 212 t.Fatal(err) 213 } 214 defer func() { 215 if err := d.Close(); err != nil { 216 t.Error(err) 217 } 218 }() 219 220 wantErr := `migration failed: syntax error at or near "TABLEE" (column 37) in line 1: CREATE TABLE foo ` + 221 `(foo text); CREATE TABLEE bar (bar text); (details: pq: syntax error at or near "TABLEE")` 222 if err := d.Run(strings.NewReader("CREATE TABLE foo (foo text); CREATE TABLEE bar (bar text);")); err == nil { 223 t.Fatal("expected err but got nil") 224 } else if err.Error() != wantErr { 225 t.Fatalf("expected '%s' but got '%s'", wantErr, err.Error()) 226 } 227 }) 228 } 229 230 func TestFilterCustomQuery(t *testing.T) { 231 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 232 ip, port, err := c.FirstPort() 233 if err != nil { 234 t.Fatal(err) 235 } 236 237 addr := fmt.Sprintf("postgres://postgres:%s@%v:%v/postgres?sslmode=disable&x-custom=foobar", 238 pgPassword, ip, port) 239 p := &Postgres{} 240 d, err := p.Open(addr) 241 if err != nil { 242 t.Fatal(err) 243 } 244 defer func() { 245 if err := d.Close(); err != nil { 246 t.Error(err) 247 } 248 }() 249 }) 250 } 251 252 func TestWithSchema(t *testing.T) { 253 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 254 ip, port, err := c.FirstPort() 255 if err != nil { 256 t.Fatal(err) 257 } 258 259 addr := pgConnectionString(ip, port) 260 p := &Postgres{} 261 d, err := p.Open(addr) 262 if err != nil { 263 t.Fatal(err) 264 } 265 defer func() { 266 if err := d.Close(); err != nil { 267 t.Fatal(err) 268 } 269 }() 270 271 // create foobar schema 272 if err := d.Run(strings.NewReader("CREATE SCHEMA foobar AUTHORIZATION postgres")); err != nil { 273 t.Fatal(err) 274 } 275 if err := d.SetVersion(1, false); err != nil { 276 t.Fatal(err) 277 } 278 279 // re-connect using that schema 280 d2, err := p.Open(fmt.Sprintf("postgres://postgres:%s@%v:%v/postgres?sslmode=disable&search_path=foobar", 281 pgPassword, ip, port)) 282 if err != nil { 283 t.Fatal(err) 284 } 285 defer func() { 286 if err := d2.Close(); err != nil { 287 t.Fatal(err) 288 } 289 }() 290 291 version, _, err := d2.Version() 292 if err != nil { 293 t.Fatal(err) 294 } 295 if version != database.NilVersion { 296 t.Fatal("expected NilVersion") 297 } 298 299 // now update version and compare 300 if err := d2.SetVersion(2, false); err != nil { 301 t.Fatal(err) 302 } 303 version, _, err = d2.Version() 304 if err != nil { 305 t.Fatal(err) 306 } 307 if version != 2 { 308 t.Fatal("expected version 2") 309 } 310 311 // meanwhile, the public schema still has the other version 312 version, _, err = d.Version() 313 if err != nil { 314 t.Fatal(err) 315 } 316 if version != 1 { 317 t.Fatal("expected version 2") 318 } 319 }) 320 } 321 322 func TestMigrationTableOption(t *testing.T) { 323 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 324 ip, port, err := c.FirstPort() 325 if err != nil { 326 t.Fatal(err) 327 } 328 329 addr := pgConnectionString(ip, port) 330 p := &Postgres{} 331 d, _ := p.Open(addr) 332 defer func() { 333 if err := d.Close(); err != nil { 334 t.Fatal(err) 335 } 336 }() 337 338 // create migrate schema 339 if err := d.Run(strings.NewReader("CREATE SCHEMA migrate AUTHORIZATION postgres")); err != nil { 340 t.Fatal(err) 341 } 342 343 // bad unquoted x-migrations-table parameter 344 wantErr := "x-migrations-table must be quoted (for instance '\"migrate\".\"schema_migrations\"') when x-migrations-table-quoted is enabled, current value is: migrate.schema_migrations" 345 d, err = p.Open(fmt.Sprintf("postgres://postgres:%s@%v:%v/postgres?sslmode=disable&x-migrations-table=migrate.schema_migrations&x-migrations-table-quoted=1", 346 pgPassword, ip, port)) 347 if (err != nil) && (err.Error() != wantErr) { 348 t.Fatalf("expected '%s' but got '%s'", wantErr, err.Error()) 349 } 350 351 // too many quoted x-migrations-table parameters 352 wantErr = "\"\"migrate\".\"schema_migrations\".\"toomany\"\" MigrationsTable contains too many dot characters" 353 d, err = p.Open(fmt.Sprintf("postgres://postgres:%s@%v:%v/postgres?sslmode=disable&x-migrations-table=\"migrate\".\"schema_migrations\".\"toomany\"&x-migrations-table-quoted=1", 354 pgPassword, ip, port)) 355 if (err != nil) && (err.Error() != wantErr) { 356 t.Fatalf("expected '%s' but got '%s'", wantErr, err.Error()) 357 } 358 359 // good quoted x-migrations-table parameter 360 d, err = p.Open(fmt.Sprintf("postgres://postgres:%s@%v:%v/postgres?sslmode=disable&x-migrations-table=\"migrate\".\"schema_migrations\"&x-migrations-table-quoted=1", 361 pgPassword, ip, port)) 362 if err != nil { 363 t.Fatal(err) 364 } 365 366 // make sure migrate.schema_migrations table exists 367 var exists bool 368 if err := d.(*Postgres).conn.QueryRowContext(context.Background(), "SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'schema_migrations' AND table_schema = 'migrate')").Scan(&exists); err != nil { 369 t.Fatal(err) 370 } 371 if !exists { 372 t.Fatalf("expected table migrate.schema_migrations to exist") 373 } 374 375 d, err = p.Open(fmt.Sprintf("postgres://postgres:%s@%v:%v/postgres?sslmode=disable&x-migrations-table=migrate.schema_migrations", 376 pgPassword, ip, port)) 377 if err != nil { 378 t.Fatal(err) 379 } 380 if err := d.(*Postgres).conn.QueryRowContext(context.Background(), "SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'migrate.schema_migrations' AND table_schema = (SELECT current_schema()))").Scan(&exists); err != nil { 381 t.Fatal(err) 382 } 383 if !exists { 384 t.Fatalf("expected table 'migrate.schema_migrations' to exist") 385 } 386 387 }) 388 } 389 390 func TestFailToCreateTableWithoutPermissions(t *testing.T) { 391 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 392 ip, port, err := c.FirstPort() 393 if err != nil { 394 t.Fatal(err) 395 } 396 397 addr := pgConnectionString(ip, port) 398 399 // Check that opening the postgres connection returns NilVersion 400 p := &Postgres{} 401 402 d, err := p.Open(addr) 403 404 if err != nil { 405 t.Fatal(err) 406 } 407 408 defer func() { 409 if err := d.Close(); err != nil { 410 t.Error(err) 411 } 412 }() 413 414 // create user who is not the owner. Although we're concatenating strings in an sql statement it should be fine 415 // since this is a test environment and we're not expecting to the pgPassword to be malicious 416 mustRun(t, d, []string{ 417 "CREATE USER not_owner WITH ENCRYPTED PASSWORD '" + pgPassword + "'", 418 "CREATE SCHEMA barfoo AUTHORIZATION postgres", 419 "GRANT USAGE ON SCHEMA barfoo TO not_owner", 420 "REVOKE CREATE ON SCHEMA barfoo FROM PUBLIC", 421 "REVOKE CREATE ON SCHEMA barfoo FROM not_owner", 422 }) 423 424 // re-connect using that schema 425 d2, err := p.Open(fmt.Sprintf("postgres://not_owner:%s@%v:%v/postgres?sslmode=disable&search_path=barfoo", 426 pgPassword, ip, port)) 427 428 defer func() { 429 if d2 == nil { 430 return 431 } 432 if err := d2.Close(); err != nil { 433 t.Fatal(err) 434 } 435 }() 436 437 var e *database.Error 438 if !errors.As(err, &e) || err == nil { 439 t.Fatal("Unexpected error, want permission denied error. Got: ", err) 440 } 441 442 if !strings.Contains(e.OrigErr.Error(), "permission denied for schema barfoo") { 443 t.Fatal(e) 444 } 445 446 // re-connect using that x-migrations-table and x-migrations-table-quoted 447 d2, err = p.Open(fmt.Sprintf("postgres://not_owner:%s@%v:%v/postgres?sslmode=disable&x-migrations-table=\"barfoo\".\"schema_migrations\"&x-migrations-table-quoted=1", 448 pgPassword, ip, port)) 449 450 if !errors.As(err, &e) || err == nil { 451 t.Fatal("Unexpected error, want permission denied error. Got: ", err) 452 } 453 454 if !strings.Contains(e.OrigErr.Error(), "permission denied for schema barfoo") { 455 t.Fatal(e) 456 } 457 }) 458 } 459 460 func TestCheckBeforeCreateTable(t *testing.T) { 461 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 462 ip, port, err := c.FirstPort() 463 if err != nil { 464 t.Fatal(err) 465 } 466 467 addr := pgConnectionString(ip, port) 468 469 // Check that opening the postgres connection returns NilVersion 470 p := &Postgres{} 471 472 d, err := p.Open(addr) 473 474 if err != nil { 475 t.Fatal(err) 476 } 477 478 defer func() { 479 if err := d.Close(); err != nil { 480 t.Error(err) 481 } 482 }() 483 484 // create user who is not the owner. Although we're concatenating strings in an sql statement it should be fine 485 // since this is a test environment and we're not expecting to the pgPassword to be malicious 486 mustRun(t, d, []string{ 487 "CREATE USER not_owner WITH ENCRYPTED PASSWORD '" + pgPassword + "'", 488 "CREATE SCHEMA barfoo AUTHORIZATION postgres", 489 "GRANT USAGE ON SCHEMA barfoo TO not_owner", 490 "GRANT CREATE ON SCHEMA barfoo TO not_owner", 491 }) 492 493 // re-connect using that schema 494 d2, err := p.Open(fmt.Sprintf("postgres://not_owner:%s@%v:%v/postgres?sslmode=disable&search_path=barfoo", 495 pgPassword, ip, port)) 496 497 if err != nil { 498 t.Fatal(err) 499 } 500 501 if err := d2.Close(); err != nil { 502 t.Fatal(err) 503 } 504 505 // revoke privileges 506 mustRun(t, d, []string{ 507 "REVOKE CREATE ON SCHEMA barfoo FROM PUBLIC", 508 "REVOKE CREATE ON SCHEMA barfoo FROM not_owner", 509 }) 510 511 // re-connect using that schema 512 d3, err := p.Open(fmt.Sprintf("postgres://not_owner:%s@%v:%v/postgres?sslmode=disable&search_path=barfoo", 513 pgPassword, ip, port)) 514 515 if err != nil { 516 t.Fatal(err) 517 } 518 519 version, _, err := d3.Version() 520 521 if err != nil { 522 t.Fatal(err) 523 } 524 525 if version != database.NilVersion { 526 t.Fatal("Unexpected version, want database.NilVersion. Got: ", version) 527 } 528 529 defer func() { 530 if err := d3.Close(); err != nil { 531 t.Fatal(err) 532 } 533 }() 534 }) 535 } 536 537 func TestParallelSchema(t *testing.T) { 538 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 539 ip, port, err := c.FirstPort() 540 if err != nil { 541 t.Fatal(err) 542 } 543 544 addr := pgConnectionString(ip, port) 545 p := &Postgres{} 546 d, err := p.Open(addr) 547 if err != nil { 548 t.Fatal(err) 549 } 550 defer func() { 551 if err := d.Close(); err != nil { 552 t.Error(err) 553 } 554 }() 555 556 // create foo and bar schemas 557 if err := d.Run(strings.NewReader("CREATE SCHEMA foo AUTHORIZATION postgres")); err != nil { 558 t.Fatal(err) 559 } 560 if err := d.Run(strings.NewReader("CREATE SCHEMA bar AUTHORIZATION postgres")); err != nil { 561 t.Fatal(err) 562 } 563 564 // re-connect using that schemas 565 dfoo, err := p.Open(fmt.Sprintf("postgres://postgres:%s@%v:%v/postgres?sslmode=disable&search_path=foo", 566 pgPassword, ip, port)) 567 if err != nil { 568 t.Fatal(err) 569 } 570 defer func() { 571 if err := dfoo.Close(); err != nil { 572 t.Error(err) 573 } 574 }() 575 576 dbar, err := p.Open(fmt.Sprintf("postgres://postgres:%s@%v:%v/postgres?sslmode=disable&search_path=bar", 577 pgPassword, ip, port)) 578 if err != nil { 579 t.Fatal(err) 580 } 581 defer func() { 582 if err := dbar.Close(); err != nil { 583 t.Error(err) 584 } 585 }() 586 587 if err := dfoo.Lock(); err != nil { 588 t.Fatal(err) 589 } 590 591 if err := dbar.Lock(); err != nil { 592 t.Fatal(err) 593 } 594 595 if err := dbar.Unlock(); err != nil { 596 t.Fatal(err) 597 } 598 599 if err := dfoo.Unlock(); err != nil { 600 t.Fatal(err) 601 } 602 }) 603 } 604 605 func TestPostgres_Lock(t *testing.T) { 606 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 607 ip, port, err := c.FirstPort() 608 if err != nil { 609 t.Fatal(err) 610 } 611 612 addr := pgConnectionString(ip, port) 613 p := &Postgres{} 614 d, err := p.Open(addr) 615 if err != nil { 616 t.Fatal(err) 617 } 618 619 dt.Test(t, d, []byte("SELECT 1")) 620 621 ps := d.(*Postgres) 622 623 err = ps.Lock() 624 if err != nil { 625 t.Fatal(err) 626 } 627 628 err = ps.Unlock() 629 if err != nil { 630 t.Fatal(err) 631 } 632 633 err = ps.Lock() 634 if err != nil { 635 t.Fatal(err) 636 } 637 638 err = ps.Unlock() 639 if err != nil { 640 t.Fatal(err) 641 } 642 }) 643 } 644 645 func TestWithInstance_Concurrent(t *testing.T) { 646 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 647 ip, port, err := c.FirstPort() 648 if err != nil { 649 t.Fatal(err) 650 } 651 652 // The number of concurrent processes running WithInstance 653 const concurrency = 30 654 655 // We can instantiate a single database handle because it is 656 // actually a connection pool, and so, each of the below go 657 // routines will have a high probability of using a separate 658 // connection, which is something we want to exercise. 659 db, err := sql.Open("postgres", pgConnectionString(ip, port)) 660 if err != nil { 661 t.Fatal(err) 662 } 663 defer func() { 664 if err := db.Close(); err != nil { 665 t.Error(err) 666 } 667 }() 668 669 db.SetMaxIdleConns(concurrency) 670 db.SetMaxOpenConns(concurrency) 671 672 var wg sync.WaitGroup 673 defer wg.Wait() 674 675 wg.Add(concurrency) 676 for i := 0; i < concurrency; i++ { 677 go func(i int) { 678 defer wg.Done() 679 _, err := WithInstance(db, &Config{}) 680 if err != nil { 681 t.Errorf("process %d error: %s", i, err) 682 } 683 }(i) 684 } 685 }) 686 } 687 688 func TestWithConnection(t *testing.T) { 689 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 690 ip, port, err := c.FirstPort() 691 if err != nil { 692 t.Fatal(err) 693 } 694 695 db, err := sql.Open("postgres", pgConnectionString(ip, port)) 696 if err != nil { 697 t.Fatal(err) 698 } 699 defer func() { 700 if err := db.Close(); err != nil { 701 t.Error(err) 702 } 703 }() 704 705 ctx := context.Background() 706 conn, err := db.Conn(ctx) 707 if err != nil { 708 t.Fatal(err) 709 } 710 711 p, err := WithConnection(ctx, conn, &Config{}) 712 if err != nil { 713 t.Fatal(err) 714 } 715 716 defer func() { 717 if err := p.Close(); err != nil { 718 t.Error(err) 719 } 720 }() 721 dt.Test(t, p, []byte("SELECT 1")) 722 }) 723 } 724 725 func Test_computeLineFromPos(t *testing.T) { 726 testcases := []struct { 727 pos int 728 wantLine uint 729 wantCol uint 730 input string 731 wantOk bool 732 }{ 733 { 734 15, 2, 6, "SELECT *\nFROM foo", true, // foo table does not exists 735 }, 736 { 737 16, 3, 6, "SELECT *\n\nFROM foo", true, // foo table does not exists, empty line 738 }, 739 { 740 25, 3, 7, "SELECT *\nFROM foo\nWHERE x", true, // x column error 741 }, 742 { 743 27, 5, 7, "SELECT *\n\nFROM foo\n\nWHERE x", true, // x column error, empty lines 744 }, 745 { 746 10, 2, 1, "SELECT *\nFROMM foo", true, // FROMM typo 747 }, 748 { 749 11, 3, 1, "SELECT *\n\nFROMM foo", true, // FROMM typo, empty line 750 }, 751 { 752 17, 2, 8, "SELECT *\nFROM foo", true, // last character 753 }, 754 { 755 18, 0, 0, "SELECT *\nFROM foo", false, // invalid position 756 }, 757 } 758 for i, tc := range testcases { 759 t.Run("tc"+strconv.Itoa(i), func(t *testing.T) { 760 run := func(crlf bool, nonASCII bool) { 761 var name string 762 if crlf { 763 name = "crlf" 764 } else { 765 name = "lf" 766 } 767 if nonASCII { 768 name += "-nonascii" 769 } else { 770 name += "-ascii" 771 } 772 t.Run(name, func(t *testing.T) { 773 input := tc.input 774 if crlf { 775 input = strings.Replace(input, "\n", "\r\n", -1) 776 } 777 if nonASCII { 778 input = strings.Replace(input, "FROM", "FRÖM", -1) 779 } 780 gotLine, gotCol, gotOK := computeLineFromPos(input, tc.pos) 781 782 if tc.wantOk { 783 t.Logf("pos %d, want %d:%d, %#v", tc.pos, tc.wantLine, tc.wantCol, input) 784 } 785 786 if gotOK != tc.wantOk { 787 t.Fatalf("expected ok %v but got %v", tc.wantOk, gotOK) 788 } 789 if gotLine != tc.wantLine { 790 t.Fatalf("expected line %d but got %d", tc.wantLine, gotLine) 791 } 792 if gotCol != tc.wantCol { 793 t.Fatalf("expected col %d but got %d", tc.wantCol, gotCol) 794 } 795 }) 796 } 797 run(false, false) 798 run(true, false) 799 run(false, true) 800 run(true, true) 801 }) 802 } 803 }