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