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 }