github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/pkg/utils/util_test.go (about)

     1  // Copyright 2019 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package utils
    15  
    16  import (
    17  	"context"
    18  	"errors"
    19  	"os"
    20  	"testing"
    21  	"time"
    22  
    23  	"github.com/go-mysql-org/go-mysql/mysql"
    24  	gmysql "github.com/go-sql-driver/mysql"
    25  	"github.com/pingcap/tidb/pkg/errno"
    26  	"github.com/stretchr/testify/require"
    27  )
    28  
    29  func TestDecodeBinlogPosition(t *testing.T) {
    30  	t.Parallel()
    31  	testCases := []struct {
    32  		pos      string
    33  		isErr    bool
    34  		expecetd *mysql.Position
    35  	}{
    36  		{"()", true, nil},
    37  		{"(,}", true, nil},
    38  		{"(,)", true, nil},
    39  		{"(mysql-bin.00001,154)", false, &mysql.Position{Name: "mysql-bin.00001", Pos: 154}},
    40  		{"(mysql-bin.00001, 154)", false, &mysql.Position{Name: "mysql-bin.00001", Pos: 154}},
    41  		{"(mysql-bin.00001\t,  154)", false, &mysql.Position{Name: "mysql-bin.00001", Pos: 154}},
    42  	}
    43  
    44  	for _, tc := range testCases {
    45  		pos, err := DecodeBinlogPosition(tc.pos)
    46  		if tc.isErr {
    47  			require.Error(t, err)
    48  		} else {
    49  			require.NoError(t, err)
    50  			require.Equal(t, tc.expecetd, pos)
    51  		}
    52  	}
    53  }
    54  
    55  func TestWaitSomething(t *testing.T) {
    56  	t.Parallel()
    57  	var (
    58  		backoff  = 10
    59  		waitTime = 10 * time.Millisecond
    60  		count    = 0
    61  	)
    62  
    63  	// wait fail
    64  	f1 := func() bool {
    65  		count++
    66  		return false
    67  	}
    68  	require.False(t, WaitSomething(backoff, waitTime, f1))
    69  	require.Equal(t, backoff, count)
    70  
    71  	count = 0 // reset
    72  	// wait success
    73  	f2 := func() bool {
    74  		count++
    75  		return count >= 5
    76  	}
    77  
    78  	require.True(t, WaitSomething(backoff, waitTime, f2))
    79  	require.Equal(t, 5, count)
    80  }
    81  
    82  func TestUnwrapScheme(t *testing.T) {
    83  	t.Parallel()
    84  	cases := []struct {
    85  		old string
    86  		new string
    87  	}{
    88  		{
    89  			"http://0.0.0.0:123",
    90  			"0.0.0.0:123",
    91  		},
    92  		{
    93  			"https://0.0.0.0:123",
    94  			"0.0.0.0:123",
    95  		},
    96  		{
    97  			"http://abc.com:123",
    98  			"abc.com:123",
    99  		},
   100  		{
   101  			"httpsdfpoje.com",
   102  			"httpsdfpoje.com",
   103  		},
   104  		{
   105  			"",
   106  			"",
   107  		},
   108  	}
   109  	for _, ca := range cases {
   110  		require.Equal(t, ca.new, UnwrapScheme(ca.old))
   111  	}
   112  }
   113  
   114  func TestWrapSchemes(t *testing.T) {
   115  	t.Parallel()
   116  	cases := []struct {
   117  		old   string
   118  		http  string
   119  		https string
   120  	}{
   121  		{
   122  			"0.0.0.0:123",
   123  			"http://0.0.0.0:123",
   124  			"https://0.0.0.0:123",
   125  		},
   126  		{
   127  			"abc.com:123",
   128  			"http://abc.com:123",
   129  			"https://abc.com:123",
   130  		},
   131  		{
   132  			"abc.com:123,http://abc.com:123,0.0.0.0:123,https://0.0.0.0:123",
   133  			"http://abc.com:123,http://abc.com:123,http://0.0.0.0:123,http://0.0.0.0:123",
   134  			"https://abc.com:123,https://abc.com:123,https://0.0.0.0:123,https://0.0.0.0:123",
   135  		},
   136  		{
   137  			"",
   138  			"",
   139  			"",
   140  		},
   141  	}
   142  	for _, ca := range cases {
   143  		require.Equal(t, ca.http, WrapSchemes(ca.old, false))
   144  		require.Equal(t, ca.https, WrapSchemes(ca.old, true))
   145  	}
   146  }
   147  
   148  func TestWrapSchemesForInitialCluster(t *testing.T) {
   149  	t.Parallel()
   150  	require.Equal(t, "master1=http://127.0.0.1:8291,master2=http://127.0.0.1:8292,master3=http://127.0.0.1:8293",
   151  		WrapSchemesForInitialCluster("master1=http://127.0.0.1:8291,master2=http://127.0.0.1:8292,master3=http://127.0.0.1:8293", false))
   152  	require.Equal(t, "master1=https://127.0.0.1:8291,master2=https://127.0.0.1:8292,master3=https://127.0.0.1:8293",
   153  		WrapSchemesForInitialCluster("master1=http://127.0.0.1:8291,master2=http://127.0.0.1:8292,master3=http://127.0.0.1:8293", true))
   154  
   155  	// correct `http` or `https` for some URLs
   156  	require.Equal(t, "master1=http://127.0.0.1:8291,master2=http://127.0.0.1:8292,master3=http://127.0.0.1:8293",
   157  		WrapSchemesForInitialCluster("master1=http://127.0.0.1:8291,master2=127.0.0.1:8292,master3=https://127.0.0.1:8293", false))
   158  	require.Equal(t, "master1=https://127.0.0.1:8291,master2=https://127.0.0.1:8292,master3=https://127.0.0.1:8293",
   159  		WrapSchemesForInitialCluster("master1=http://127.0.0.1:8291,master2=127.0.0.1:8292,master3=https://127.0.0.1:8293", true))
   160  
   161  	// add `http` or `https` for all URLs
   162  	require.Equal(t, "master1=http://127.0.0.1:8291,master2=http://127.0.0.1:8292,master3=http://127.0.0.1:8293",
   163  		WrapSchemesForInitialCluster("master1=127.0.0.1:8291,master2=127.0.0.1:8292,master3=127.0.0.1:8293", false))
   164  	require.Equal(t, "master1=https://127.0.0.1:8291,master2=https://127.0.0.1:8292,master3=https://127.0.0.1:8293",
   165  		WrapSchemesForInitialCluster("master1=127.0.0.1:8291,master2=127.0.0.1:8292,master3=127.0.0.1:8293", true))
   166  }
   167  
   168  func TestIsContextCanceledError(t *testing.T) {
   169  	t.Parallel()
   170  	require.True(t, IsContextCanceledError(context.Canceled))
   171  	require.False(t, IsContextCanceledError(context.DeadlineExceeded))
   172  	require.False(t, IsContextCanceledError(errors.New("another error")))
   173  }
   174  
   175  func newMysqlErr(number uint16, message string) *gmysql.MySQLError {
   176  	return &gmysql.MySQLError{
   177  		Number:  number,
   178  		Message: message,
   179  	}
   180  }
   181  
   182  func TestIgnoreErrorCheckpoint(t *testing.T) {
   183  	t.Parallel()
   184  	require.True(t, IgnoreErrorCheckpoint(newMysqlErr(errno.ErrDupFieldName, "Duplicate column name c1")))
   185  	require.False(t, IgnoreErrorCheckpoint(newMysqlErr(errno.ErrTableExists, "Table tbl already exists")))
   186  	require.False(t, IgnoreErrorCheckpoint(errors.New("another error")))
   187  }
   188  
   189  func TestIsBuildInSkipDDL(t *testing.T) {
   190  	t.Parallel()
   191  	require.False(t, IsBuildInSkipDDL("alter table tbl add column c1 int"))
   192  	require.True(t, IsBuildInSkipDDL("DROP PROCEDURE"))
   193  
   194  	cases := []struct {
   195  		sql           string
   196  		expectSkipped bool
   197  	}{
   198  		{"SAVEPOINT `a1`", true},
   199  
   200  		// flush
   201  		{"flush privileges", true},
   202  		{"flush logs", true},
   203  		{"FLUSH TABLES WITH READ LOCK", true},
   204  
   205  		// table maintenance
   206  		{"OPTIMIZE TABLE foo", true},
   207  		{"ANALYZE TABLE foo", true},
   208  		{"REPAIR TABLE foo", true},
   209  
   210  		// temporary table
   211  		{"DROP /*!40005 TEMPORARY */ TABLE IF EXISTS `h2`", true},
   212  		{"DROP TEMPORARY TABLE IF EXISTS `foo`.`bar` /* generated by server */", true},
   213  		{"DROP TABLE foo.bar", false},
   214  		{"DROP TABLE `TEMPORARY TABLE`", false},
   215  		{"DROP TABLE `TEMPORARY TABLE` /* generated by server */", false},
   216  
   217  		// trigger
   218  		{"CREATE DEFINER=`root`@`%` TRIGGER ins_sum BEFORE INSERT ON bar FOR EACH ROW SET @sum = @sum + NEW.id", true},
   219  		{"CREATE TRIGGER ins_sum BEFORE INSERT ON bar FOR EACH ROW SET @sum = @sum + NEW.id", true},
   220  		{"DROP TRIGGER ins_sum", true},
   221  		{"create table `trigger`(id int)", false},
   222  
   223  		// procedure
   224  		{"drop procedure if exists prepare_data", true},
   225  		{"CREATE DEFINER=`root`@`%` PROCEDURE `simpleproc`(OUT param1 INT) BEGIN  select count(*) into param1 from shard_0001; END", true},
   226  		{"CREATE PROCEDURE simpleproc(OUT param1 INT) BEGIN  select count(*) into param1 from shard_0001; END", true},
   227  		{"alter procedure prepare_data comment 'i am a comment'", true},
   228  		{"create table `procedure`(id int)", false},
   229  
   230  		{`CREATE DEFINER=root@localhost PROCEDURE simpleproc(OUT param1 INT)
   231  BEGIN
   232      SELECT COUNT(*) INTO param1 FROM t;
   233  END`, true},
   234  
   235  		// view
   236  		{"CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`%` SQL SECURITY DEFINER VIEW `v` AS SELECT qty, price, qty*price AS value FROM t", true},
   237  		{"CREATE OR REPLACE ALGORITHM=UNDEFINED DEFINER=`root`@`%` SQL SECURITY DEFINER VIEW `v` AS SELECT qty, price, qty*price AS value FROM t", true},
   238  		{"ALTER ALGORITHM=UNDEFINED DEFINER=`root`@`%` SQL SECURITY DEFINER VIEW `v` AS SELECT qty, price, qty*price AS value FROM t", true},
   239  		{"DROP VIEW v", true},
   240  		{"CREATE TABLE `VIEW`(id int)", false},
   241  		{"ALTER TABLE `VIEW`(id int)", false},
   242  
   243  		// function
   244  		{"CREATE FUNCTION metaphon RETURNS STRING SONAME 'udf_example.so'", true},
   245  		{"CREATE AGGREGATE FUNCTION avgcost RETURNS REAL SONAME 'udf_example.so'", true},
   246  		{"DROP FUNCTION metaphon", true},
   247  		{"DROP FUNCTION IF EXISTS `rand_string`", true},
   248  		{"ALTER FUNCTION metaphon COMMENT 'hh'", true},
   249  		{"CREATE TABLE `function` (id int)", false},
   250  
   251  		{`CREATE DEFINER=root@localhost FUNCTION rand_string(n INT) RETURNS varchar(255) CHARSET utf8
   252  BEGIN
   253            DECLARE chars_str VARCHAR(100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
   254            DECLARE return_str VARCHAR(255) DEFAULT '';
   255            DECLARE i INT DEFAULT 0;
   256            WHILE i<n DO
   257                SET return_str = CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));
   258                SET i = i+1;
   259            END WHILE;
   260      RETURN return_str;
   261  END`, true},
   262  
   263  		// tablespace
   264  		{"CREATE TABLESPACE `ts1` ADD DATAFILE 'ts1.ibd' ENGINE=INNODB", true},
   265  		{"ALTER TABLESPACE `ts1` DROP DATAFILE 'ts1.idb' ENGIEN=NDB", true},
   266  		{"DROP TABLESPACE ts1", true},
   267  
   268  		// event
   269  		{"CREATE DEFINER=CURRENT_USER EVENT myevent ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 HOUR DO UPDATE myschema.mytable SET mycol = mycol + 1;", true},
   270  		{"ALTER DEFINER = CURRENT_USER EVENT myevent ON SCHEDULE EVERY 12 HOUR STARTS CURRENT_TIMESTAMP + INTERVAL 4 HOUR;", true},
   271  		{"DROP EVENT myevent;", true},
   272  
   273  		// account management
   274  		{"CREATE USER 't'@'%' IDENTIFIED WITH 'mysql_native_password' AS '*93E34F4B81FEC9E8271655EA87646ED01AF377CC'", true},
   275  		{"ALTER USER 't'@'%' IDENTIFIED WITH 'mysql_native_password' AS '*1114744159A0EF13B12FC371C94877763F9512D0'", true},
   276  		{"rename user t to 1", true},
   277  		{"drop user t1", true},
   278  		{"GRANT ALL PRIVILEGES ON *.* TO 't2'@'%' IDENTIFIED WITH 'mysql_native_password' AS '*12033B78389744F3F39AC4CE4CCFCAD6960D8EA0'", true},
   279  		{"revoke reload on *.* from 't2'@'%'", true},
   280  	}
   281  	for _, ca := range cases {
   282  		require.Equal(t, ca.expectSkipped, IsBuildInSkipDDL(ca.sql))
   283  	}
   284  }
   285  
   286  func TestProxyFields(t *testing.T) {
   287  	t.Parallel()
   288  	revIndex := map[string]int{
   289  		"http_proxy":  0,
   290  		"https_proxy": 1,
   291  		"no_proxy":    2,
   292  	}
   293  	envs := []string{"http_proxy", "https_proxy", "no_proxy"}
   294  	envPreset := []string{"http://127.0.0.1:8080", "https://127.0.0.1:8443", "localhost,127.0.0.1"}
   295  
   296  	// Exhaust all combinations of those environment variables' selection.
   297  	// Each bit of the mask decided whether this index of `envs` would be set.
   298  	for mask := 0; mask <= 0b111; mask++ {
   299  		for _, env := range envs {
   300  			require.NoError(t, os.Unsetenv(env))
   301  		}
   302  
   303  		for i := 0; i < 3; i++ {
   304  			if (1<<i)&mask != 0 {
   305  				require.NoError(t, os.Setenv(envs[i], envPreset[i]))
   306  			}
   307  		}
   308  
   309  		for _, field := range proxyFields() {
   310  			idx, ok := revIndex[field.Key]
   311  			require.True(t, ok)
   312  			require.NotEqual(t, 0, (1<<idx)&mask)
   313  			require.Equal(t, envPreset[idx], field.String)
   314  		}
   315  	}
   316  }