github.com/pingcap/tidb-lightning@v5.0.0-rc.0.20210428090220-84b649866577+incompatible/lightning/config/config_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 config_test
    15  
    16  import (
    17  	"bytes"
    18  	"context"
    19  	"flag"
    20  	"fmt"
    21  	"net"
    22  	"net/http"
    23  	"net/http/httptest"
    24  	"net/url"
    25  	"path/filepath"
    26  	"regexp"
    27  	"strconv"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/BurntSushi/toml"
    32  	. "github.com/pingcap/check"
    33  	"github.com/pingcap/parser/mysql"
    34  
    35  	"github.com/pingcap/tidb-lightning/lightning/config"
    36  )
    37  
    38  func Test(t *testing.T) {
    39  	TestingT(t)
    40  }
    41  
    42  var _ = Suite(&configTestSuite{})
    43  
    44  type configTestSuite struct{}
    45  
    46  func startMockServer(c *C, statusCode int, content string) (*httptest.Server, string, int) {
    47  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    48  		w.WriteHeader(statusCode)
    49  		fmt.Fprint(w, content)
    50  	}))
    51  
    52  	url, err := url.Parse(ts.URL)
    53  	c.Assert(err, IsNil)
    54  	host, portString, err := net.SplitHostPort(url.Host)
    55  	c.Assert(err, IsNil)
    56  	port, err := strconv.Atoi(portString)
    57  	c.Assert(err, IsNil)
    58  
    59  	return ts, host, port
    60  }
    61  
    62  func assignMinimalLegalValue(cfg *config.Config) {
    63  	cfg.TiDB.Host = "123.45.67.89"
    64  	cfg.TiDB.Port = 4567
    65  	cfg.TiDB.StatusPort = 8901
    66  	cfg.TiDB.PdAddr = "234.56.78.90:12345"
    67  	cfg.Mydumper.SourceDir = "file://."
    68  	cfg.TikvImporter.DiskQuota = 1
    69  }
    70  
    71  func (s *configTestSuite) TestAdjustPdAddrAndPort(c *C) {
    72  	ts, host, port := startMockServer(c, http.StatusOK,
    73  		`{"port":4444,"advertise-address":"","path":"123.45.67.89:1234,56.78.90.12:3456"}`,
    74  	)
    75  	defer ts.Close()
    76  
    77  	cfg := config.NewConfig()
    78  	cfg.TiDB.Host = host
    79  	cfg.TiDB.StatusPort = port
    80  	cfg.Mydumper.SourceDir = "."
    81  
    82  	err := cfg.Adjust(context.Background())
    83  	c.Assert(err, IsNil)
    84  	c.Assert(cfg.TiDB.Port, Equals, 4444)
    85  	c.Assert(cfg.TiDB.PdAddr, Equals, "123.45.67.89:1234")
    86  }
    87  
    88  func (s *configTestSuite) TestAdjustPdAddrAndPortViaAdvertiseAddr(c *C) {
    89  	ts, host, port := startMockServer(c, http.StatusOK,
    90  		`{"port":6666,"advertise-address":"121.212.121.212:5555","path":"34.34.34.34:3434"}`,
    91  	)
    92  	defer ts.Close()
    93  
    94  	cfg := config.NewConfig()
    95  	cfg.TiDB.Host = host
    96  	cfg.TiDB.StatusPort = port
    97  	cfg.Mydumper.SourceDir = "."
    98  
    99  	err := cfg.Adjust(context.Background())
   100  	c.Assert(err, IsNil)
   101  	c.Assert(cfg.TiDB.Port, Equals, 6666)
   102  	c.Assert(cfg.TiDB.PdAddr, Equals, "34.34.34.34:3434")
   103  }
   104  
   105  func (s *configTestSuite) TestAdjustPageNotFound(c *C) {
   106  	ts, host, port := startMockServer(c, http.StatusNotFound, "{}")
   107  	defer ts.Close()
   108  
   109  	cfg := config.NewConfig()
   110  	cfg.TiDB.Host = host
   111  	cfg.TiDB.StatusPort = port
   112  
   113  	err := cfg.Adjust(context.Background())
   114  	c.Assert(err, ErrorMatches, "cannot fetch settings from TiDB.*")
   115  }
   116  
   117  func (s *configTestSuite) TestAdjustConnectRefused(c *C) {
   118  	ts, host, port := startMockServer(c, http.StatusOK, "{}")
   119  
   120  	cfg := config.NewConfig()
   121  	cfg.TiDB.Host = host
   122  	cfg.TiDB.StatusPort = port
   123  
   124  	ts.Close() // immediately close to ensure connection refused.
   125  
   126  	err := cfg.Adjust(context.Background())
   127  	c.Assert(err, ErrorMatches, "cannot fetch settings from TiDB.*")
   128  }
   129  
   130  func (s *configTestSuite) TestAdjustInvalidBackend(c *C) {
   131  	cfg := config.NewConfig()
   132  	cfg.TikvImporter.Backend = "no_such_backend"
   133  	err := cfg.Adjust(context.Background())
   134  	c.Assert(err, ErrorMatches, "invalid config: unsupported `tikv-importer\\.backend` \\(no_such_backend\\)")
   135  }
   136  
   137  func (s *configTestSuite) TestAdjustFileRoutePath(c *C) {
   138  	cfg := config.NewConfig()
   139  	assignMinimalLegalValue(cfg)
   140  
   141  	ctx := context.Background()
   142  	tmpDir := c.MkDir()
   143  	cfg.Mydumper.SourceDir = tmpDir
   144  	invalidPath := filepath.Join(tmpDir, "../test123/1.sql")
   145  	rule := &config.FileRouteRule{Path: invalidPath, Type: "sql", Schema: "test", Table: "tbl"}
   146  	cfg.Mydumper.FileRouters = []*config.FileRouteRule{rule}
   147  	err := cfg.Adjust(ctx)
   148  	c.Assert(err, ErrorMatches, fmt.Sprintf("\\Qfile route path '%s' is not in source dir '%s'\\E", invalidPath, tmpDir))
   149  
   150  	relPath := filepath.FromSlash("test_dir/1.sql")
   151  	rule.Path = filepath.Join(tmpDir, relPath)
   152  	err = cfg.Adjust(ctx)
   153  	c.Assert(err, IsNil)
   154  	c.Assert(cfg.Mydumper.FileRouters[0].Path, Equals, relPath)
   155  }
   156  
   157  func (s *configTestSuite) TestDecodeError(c *C) {
   158  	ts, host, port := startMockServer(c, http.StatusOK, "invalid-string")
   159  	defer ts.Close()
   160  
   161  	cfg := config.NewConfig()
   162  	cfg.TiDB.Host = host
   163  	cfg.TiDB.StatusPort = port
   164  
   165  	err := cfg.Adjust(context.Background())
   166  	c.Assert(err, ErrorMatches, "cannot fetch settings from TiDB.*")
   167  }
   168  
   169  func (s *configTestSuite) TestInvalidSetting(c *C) {
   170  	ts, host, port := startMockServer(c, http.StatusOK, `{"port": 0}`)
   171  	defer ts.Close()
   172  
   173  	cfg := config.NewConfig()
   174  	cfg.TiDB.Host = host
   175  	cfg.TiDB.StatusPort = port
   176  
   177  	err := cfg.Adjust(context.Background())
   178  	c.Assert(err, ErrorMatches, "invalid `tidb.port` setting")
   179  }
   180  
   181  func (s *configTestSuite) TestInvalidPDAddr(c *C) {
   182  	ts, host, port := startMockServer(c, http.StatusOK, `{"port": 1234, "path": ",,"}`)
   183  	defer ts.Close()
   184  
   185  	cfg := config.NewConfig()
   186  	cfg.TiDB.Host = host
   187  	cfg.TiDB.StatusPort = port
   188  
   189  	err := cfg.Adjust(context.Background())
   190  	c.Assert(err, ErrorMatches, "invalid `tidb.pd-addr` setting")
   191  }
   192  
   193  func (s *configTestSuite) TestAdjustWillNotContactServerIfEverythingIsDefined(c *C) {
   194  	cfg := config.NewConfig()
   195  	assignMinimalLegalValue(cfg)
   196  
   197  	err := cfg.Adjust(context.Background())
   198  	c.Assert(err, IsNil)
   199  	c.Assert(cfg.TiDB.Port, Equals, 4567)
   200  	c.Assert(cfg.TiDB.PdAddr, Equals, "234.56.78.90:12345")
   201  }
   202  
   203  func (s *configTestSuite) TestAdjustWillBatchImportRatioInvalid(c *C) {
   204  	cfg := config.NewConfig()
   205  	assignMinimalLegalValue(cfg)
   206  	cfg.Mydumper.BatchImportRatio = -1
   207  	err := cfg.Adjust(context.Background())
   208  	c.Assert(err, IsNil)
   209  	c.Assert(cfg.Mydumper.BatchImportRatio, Equals, 0.75)
   210  }
   211  
   212  func (s *configTestSuite) TestAdjustSecuritySection(c *C) {
   213  	testCases := []struct {
   214  		input       string
   215  		expectedCA  string
   216  		expectedTLS string
   217  	}{
   218  		{
   219  			input:       ``,
   220  			expectedCA:  "",
   221  			expectedTLS: "false",
   222  		},
   223  		{
   224  			input: `
   225  				[security]
   226  			`,
   227  			expectedCA:  "",
   228  			expectedTLS: "false",
   229  		},
   230  		{
   231  			input: `
   232  				[security]
   233  				ca-path = "/path/to/ca.pem"
   234  			`,
   235  			expectedCA:  "/path/to/ca.pem",
   236  			expectedTLS: "cluster",
   237  		},
   238  		{
   239  			input: `
   240  				[security]
   241  				ca-path = "/path/to/ca.pem"
   242  				[tidb.security]
   243  			`,
   244  			expectedCA:  "",
   245  			expectedTLS: "false",
   246  		},
   247  		{
   248  			input: `
   249  				[security]
   250  				ca-path = "/path/to/ca.pem"
   251  				[tidb.security]
   252  				ca-path = "/path/to/ca2.pem"
   253  			`,
   254  			expectedCA:  "/path/to/ca2.pem",
   255  			expectedTLS: "cluster",
   256  		},
   257  		{
   258  			input: `
   259  				[security]
   260  				[tidb.security]
   261  				ca-path = "/path/to/ca2.pem"
   262  			`,
   263  			expectedCA:  "/path/to/ca2.pem",
   264  			expectedTLS: "cluster",
   265  		},
   266  		{
   267  			input: `
   268  				[security]
   269  				[tidb]
   270  				tls = "skip-verify"
   271  				[tidb.security]
   272  			`,
   273  			expectedCA:  "",
   274  			expectedTLS: "skip-verify",
   275  		},
   276  	}
   277  
   278  	for _, tc := range testCases {
   279  		comment := Commentf("input = %s", tc.input)
   280  
   281  		cfg := config.NewConfig()
   282  		assignMinimalLegalValue(cfg)
   283  		err := cfg.LoadFromTOML([]byte(tc.input))
   284  		c.Assert(err, IsNil, comment)
   285  
   286  		err = cfg.Adjust(context.Background())
   287  		c.Assert(err, IsNil, comment)
   288  		c.Assert(cfg.TiDB.Security.CAPath, Equals, tc.expectedCA, comment)
   289  		c.Assert(cfg.TiDB.TLS, Equals, tc.expectedTLS, comment)
   290  	}
   291  }
   292  
   293  func (s *configTestSuite) TestInvalidCSV(c *C) {
   294  	testCases := []struct {
   295  		input string
   296  		err   string
   297  	}{
   298  		{
   299  			input: `
   300  				[mydumper.csv]
   301  				separator = ''
   302  			`,
   303  			err: "invalid config: `mydumper.csv.separator` must not be empty",
   304  		},
   305  		{
   306  			input: `
   307  				[mydumper.csv]
   308  				separator = 'hello'
   309  				delimiter = 'hel'
   310  			`,
   311  			err: "invalid config: `mydumper.csv.separator` and `mydumper.csv.delimiter` must not be prefix of each other",
   312  		},
   313  		{
   314  			input: `
   315  				[mydumper.csv]
   316  				separator = 'hel'
   317  				delimiter = 'hello'
   318  			`,
   319  			err: "invalid config: `mydumper.csv.separator` and `mydumper.csv.delimiter` must not be prefix of each other",
   320  		},
   321  		{
   322  			input: `
   323  				[mydumper.csv]
   324  				separator = '\'
   325  				backslash-escape = false
   326  			`,
   327  			err: "",
   328  		},
   329  		{
   330  			input: `
   331  				[mydumper.csv]
   332  				separator = ','
   333  			`,
   334  			err: "",
   335  		},
   336  		{
   337  			input: `
   338  				[mydumper.csv]
   339  				delimiter = ''
   340  			`,
   341  			err: "",
   342  		},
   343  		{
   344  			input: `
   345  				[mydumper.csv]
   346  				delimiter = 'hello'
   347  			`,
   348  			err: "",
   349  		},
   350  		{
   351  			input: `
   352  				[mydumper.csv]
   353  				delimiter = '\'
   354  				backslash-escape = false
   355  			`,
   356  			err: "",
   357  		},
   358  		{
   359  			input: `
   360  				[mydumper.csv]
   361  				separator = '\s'
   362  				delimiter = '\d'
   363  			`,
   364  			err: "",
   365  		},
   366  		{
   367  			input: `
   368  				[mydumper.csv]
   369  				separator = '|'
   370  				delimiter = '|'
   371  			`,
   372  			err: "invalid config: `mydumper.csv.separator` and `mydumper.csv.delimiter` must not be prefix of each other",
   373  		},
   374  		{
   375  			input: `
   376  				[mydumper.csv]
   377  				separator = '\'
   378  				backslash-escape = true
   379  			`,
   380  			err: "invalid config: cannot use '\\' as CSV separator when `mydumper.csv.backslash-escape` is true",
   381  		},
   382  		{
   383  			input: `
   384  				[mydumper.csv]
   385  				delimiter = '\'
   386  				backslash-escape = true
   387  			`,
   388  			err: "invalid config: cannot use '\\' as CSV delimiter when `mydumper.csv.backslash-escape` is true",
   389  		},
   390  		{
   391  			input: `
   392  				[tidb]
   393  				sql-mode = "invalid-sql-mode"
   394  			`,
   395  			err: "invalid config: `mydumper.tidb.sql_mode` must be a valid SQL_MODE: ERROR 1231 (42000): Variable 'sql_mode' can't be set to the value of 'invalid-sql-mode'",
   396  		},
   397  		{
   398  			input: `
   399  				[[routes]]
   400  				schema-pattern = ""
   401  				table-pattern = "shard_table_*"
   402  			`,
   403  			err: "schema pattern of table route rule should not be empty",
   404  		},
   405  		{
   406  			input: `
   407  				[[routes]]
   408  				schema-pattern = "schema_*"
   409  				table-pattern = ""
   410  			`,
   411  			err: "target schema of table route rule should not be empty",
   412  		},
   413  	}
   414  
   415  	for _, tc := range testCases {
   416  		comment := Commentf("input = %s", tc.input)
   417  
   418  		cfg := config.NewConfig()
   419  		cfg.Mydumper.SourceDir = "file://."
   420  		cfg.TiDB.Port = 4000
   421  		cfg.TiDB.PdAddr = "test.invalid:2379"
   422  		err := cfg.LoadFromTOML([]byte(tc.input))
   423  		c.Assert(err, IsNil)
   424  
   425  		err = cfg.Adjust(context.Background())
   426  		if tc.err != "" {
   427  			c.Assert(err, ErrorMatches, regexp.QuoteMeta(tc.err), comment)
   428  		} else {
   429  			c.Assert(err, IsNil, comment)
   430  		}
   431  	}
   432  }
   433  
   434  func (s *configTestSuite) TestInvalidTOML(c *C) {
   435  	cfg := &config.Config{}
   436  	err := cfg.LoadFromTOML([]byte(`
   437  		invalid[mydumper.csv]
   438  		delimiter = '\'
   439  		backslash-escape = true
   440  	`))
   441  	c.Assert(err, ErrorMatches, regexp.QuoteMeta("Near line 0 (last key parsed ''): bare keys cannot contain '['"))
   442  }
   443  
   444  func (s *configTestSuite) TestTOMLUnusedKeys(c *C) {
   445  	cfg := &config.Config{}
   446  	err := cfg.LoadFromTOML([]byte(`
   447  		[lightning]
   448  		typo = 123
   449  	`))
   450  	c.Assert(err, ErrorMatches, regexp.QuoteMeta("config file contained unknown configuration options: lightning.typo"))
   451  }
   452  
   453  func (s *configTestSuite) TestDurationUnmarshal(c *C) {
   454  	duration := config.Duration{}
   455  	err := duration.UnmarshalText([]byte("13m20s"))
   456  	c.Assert(err, IsNil)
   457  	c.Assert(duration.Duration.Seconds(), Equals, 13*60+20.0)
   458  	err = duration.UnmarshalText([]byte("13x20s"))
   459  	c.Assert(err, ErrorMatches, "time: unknown unit .?x.? in duration .?13x20s.?")
   460  }
   461  
   462  func (s *configTestSuite) TestDurationMarshalJSON(c *C) {
   463  	duration := config.Duration{}
   464  	err := duration.UnmarshalText([]byte("13m20s"))
   465  	c.Assert(err, IsNil)
   466  	c.Assert(duration.Duration.Seconds(), Equals, 13*60+20.0)
   467  	result, err := duration.MarshalJSON()
   468  	c.Assert(err, IsNil)
   469  	c.Assert(string(result), Equals, `"13m20s"`)
   470  }
   471  
   472  func (s *configTestSuite) TestLoadConfig(c *C) {
   473  	cfg, err := config.LoadGlobalConfig([]string{"-tidb-port", "sss"}, nil)
   474  	c.Assert(err, ErrorMatches, `invalid value "sss" for flag -tidb-port: parse error`)
   475  	c.Assert(cfg, IsNil)
   476  
   477  	cfg, err = config.LoadGlobalConfig([]string{"-V"}, nil)
   478  	c.Assert(err, Equals, flag.ErrHelp)
   479  	c.Assert(cfg, IsNil)
   480  
   481  	cfg, err = config.LoadGlobalConfig([]string{"-config", "not-exists"}, nil)
   482  	c.Assert(err, ErrorMatches, ".*(no such file or directory|The system cannot find the file specified).*")
   483  	c.Assert(cfg, IsNil)
   484  
   485  	cfg, err = config.LoadGlobalConfig([]string{"--server-mode"}, nil)
   486  	c.Assert(err, ErrorMatches, "If server-mode is enabled, the status-addr must be a valid listen address")
   487  	c.Assert(cfg, IsNil)
   488  
   489  	path, _ := filepath.Abs(".")
   490  	cfg, err = config.LoadGlobalConfig([]string{
   491  		"-L", "debug",
   492  		"-log-file", "/path/to/file.log",
   493  		"-tidb-host", "172.16.30.11",
   494  		"-tidb-port", "4001",
   495  		"-tidb-user", "guest",
   496  		"-tidb-password", "12345",
   497  		"-pd-urls", "172.16.30.11:2379,172.16.30.12:2379",
   498  		"-d", path,
   499  		"-importer", "172.16.30.11:23008",
   500  		"-checksum=false",
   501  	}, nil)
   502  	c.Assert(err, IsNil)
   503  	c.Assert(cfg.App.Config.Level, Equals, "debug")
   504  	c.Assert(cfg.App.Config.File, Equals, "/path/to/file.log")
   505  	c.Assert(cfg.TiDB.Host, Equals, "172.16.30.11")
   506  	c.Assert(cfg.TiDB.Port, Equals, 4001)
   507  	c.Assert(cfg.TiDB.User, Equals, "guest")
   508  	c.Assert(cfg.TiDB.Psw, Equals, "12345")
   509  	c.Assert(cfg.TiDB.PdAddr, Equals, "172.16.30.11:2379,172.16.30.12:2379")
   510  	c.Assert(cfg.Mydumper.SourceDir, Equals, path)
   511  	c.Assert(cfg.TikvImporter.Addr, Equals, "172.16.30.11:23008")
   512  	c.Assert(cfg.PostRestore.Checksum, Equals, config.OpLevelOff)
   513  	c.Assert(cfg.PostRestore.Analyze, Equals, config.OpLevelOptional)
   514  
   515  	taskCfg := config.NewConfig()
   516  	err = taskCfg.LoadFromGlobal(cfg)
   517  	c.Assert(err, IsNil)
   518  	c.Assert(taskCfg.PostRestore.Checksum, Equals, config.OpLevelOff)
   519  	c.Assert(taskCfg.PostRestore.Analyze, Equals, config.OpLevelOptional)
   520  
   521  	taskCfg.Checkpoint.DSN = ""
   522  	taskCfg.Checkpoint.Driver = config.CheckpointDriverMySQL
   523  	err = taskCfg.Adjust(context.Background())
   524  	c.Assert(err, IsNil)
   525  	c.Assert(taskCfg.Checkpoint.DSN, Equals, "guest:12345@tcp(172.16.30.11:4001)/?charset=utf8mb4&sql_mode='"+mysql.DefaultSQLMode+"'&maxAllowedPacket=67108864&tls=false")
   526  
   527  	result := taskCfg.String()
   528  	c.Assert(result, Matches, `.*"pd-addr":"172.16.30.11:2379,172.16.30.12:2379".*`)
   529  }
   530  
   531  func (s *configTestSuite) TestDefaultImporterBackendValue(c *C) {
   532  	cfg := config.NewConfig()
   533  	assignMinimalLegalValue(cfg)
   534  	cfg.TikvImporter.Backend = "importer"
   535  	err := cfg.Adjust(context.Background())
   536  	c.Assert(err, IsNil)
   537  	c.Assert(cfg.App.IndexConcurrency, Equals, 2)
   538  	c.Assert(cfg.App.TableConcurrency, Equals, 6)
   539  }
   540  
   541  func (s *configTestSuite) TestDefaultTidbBackendValue(c *C) {
   542  	cfg := config.NewConfig()
   543  	assignMinimalLegalValue(cfg)
   544  	cfg.TikvImporter.Backend = "tidb"
   545  	cfg.App.RegionConcurrency = 123
   546  	err := cfg.Adjust(context.Background())
   547  	c.Assert(err, IsNil)
   548  	c.Assert(cfg.App.IndexConcurrency, Equals, 123)
   549  	c.Assert(cfg.App.TableConcurrency, Equals, 123)
   550  }
   551  
   552  func (s *configTestSuite) TestDefaultCouldBeOverwritten(c *C) {
   553  	cfg := config.NewConfig()
   554  	assignMinimalLegalValue(cfg)
   555  	cfg.TikvImporter.Backend = "importer"
   556  	cfg.App.IndexConcurrency = 20
   557  	cfg.App.TableConcurrency = 60
   558  	err := cfg.Adjust(context.Background())
   559  	c.Assert(err, IsNil)
   560  	c.Assert(cfg.App.IndexConcurrency, Equals, 20)
   561  	c.Assert(cfg.App.TableConcurrency, Equals, 60)
   562  }
   563  
   564  func (s *configTestSuite) TestLoadFromInvalidConfig(c *C) {
   565  	taskCfg := config.NewConfig()
   566  	err := taskCfg.LoadFromGlobal(&config.GlobalConfig{
   567  		ConfigFileContent: []byte("invalid toml"),
   568  	})
   569  	c.Assert(err, ErrorMatches, "Near line 1.*")
   570  }
   571  
   572  func (s *configTestSuite) TestTomlPostRestore(c *C) {
   573  	cfg := &config.Config{}
   574  	err := cfg.LoadFromTOML([]byte(`
   575  		[post-restore]
   576  		checksum = "req"
   577  	`))
   578  	c.Assert(err, ErrorMatches, regexp.QuoteMeta("invalid op level 'req', please choose valid option between ['off', 'optional', 'required']"))
   579  
   580  	err = cfg.LoadFromTOML([]byte(`
   581  		[post-restore]
   582  		analyze = 123
   583  	`))
   584  	c.Assert(err, ErrorMatches, regexp.QuoteMeta("invalid op level '123', please choose valid option between ['off', 'optional', 'required']"))
   585  
   586  	kvMap := map[string]config.PostOpLevel{
   587  		`"off"`:      config.OpLevelOff,
   588  		`"required"`: config.OpLevelRequired,
   589  		`"optional"`: config.OpLevelOptional,
   590  		"true":       config.OpLevelRequired,
   591  		"false":      config.OpLevelOff,
   592  	}
   593  
   594  	var b bytes.Buffer
   595  	enc := toml.NewEncoder(&b)
   596  
   597  	for k, v := range kvMap {
   598  		cfg := &config.Config{}
   599  		confStr := fmt.Sprintf("[post-restore]\r\nchecksum= %s\r\n", k)
   600  		err := cfg.LoadFromTOML([]byte(confStr))
   601  		c.Assert(err, IsNil)
   602  		c.Assert(cfg.PostRestore.Checksum, Equals, v)
   603  
   604  		b.Reset()
   605  		c.Assert(enc.Encode(cfg.PostRestore), IsNil)
   606  		c.Assert(&b, Matches, fmt.Sprintf(`(?s).*checksum = "\Q%s\E".*`, v))
   607  	}
   608  
   609  	for k, v := range kvMap {
   610  		cfg := &config.Config{}
   611  		confStr := fmt.Sprintf("[post-restore]\r\nanalyze= %s\r\n", k)
   612  		err := cfg.LoadFromTOML([]byte(confStr))
   613  		c.Assert(err, IsNil)
   614  		c.Assert(cfg.PostRestore.Analyze, Equals, v)
   615  
   616  		b.Reset()
   617  		c.Assert(enc.Encode(cfg.PostRestore), IsNil)
   618  		c.Assert(&b, Matches, fmt.Sprintf(`(?s).*analyze = "\Q%s\E".*`, v))
   619  	}
   620  }
   621  
   622  func (s *configTestSuite) TestCronEncodeDecode(c *C) {
   623  	cfg := &config.Config{}
   624  	cfg.Cron.SwitchMode.Duration = 1 * time.Minute
   625  	cfg.Cron.LogProgress.Duration = 2 * time.Minute
   626  	cfg.Cron.CheckDiskQuota.Duration = 3 * time.Second
   627  	var b bytes.Buffer
   628  	c.Assert(toml.NewEncoder(&b).Encode(cfg.Cron), IsNil)
   629  	c.Assert(b.String(), Equals, "switch-mode = \"1m0s\"\nlog-progress = \"2m0s\"\ncheck-disk-quota = \"3s\"\n")
   630  
   631  	confStr := "[cron]\r\n" + b.String()
   632  	cfg2 := &config.Config{}
   633  	c.Assert(cfg2.LoadFromTOML([]byte(confStr)), IsNil)
   634  	c.Assert(cfg2.Cron, DeepEquals, cfg.Cron)
   635  }
   636  
   637  func (s *configTestSuite) TestAdjustWithLegacyBlackWhiteList(c *C) {
   638  	cfg := config.NewConfig()
   639  	assignMinimalLegalValue(cfg)
   640  	c.Assert(cfg.Mydumper.Filter, DeepEquals, config.DefaultFilter)
   641  	c.Assert(cfg.HasLegacyBlackWhiteList(), IsFalse)
   642  
   643  	ctx := context.Background()
   644  	cfg.Mydumper.Filter = []string{"test.*"}
   645  	c.Assert(cfg.Adjust(ctx), IsNil)
   646  	c.Assert(cfg.HasLegacyBlackWhiteList(), IsFalse)
   647  
   648  	cfg.BWList.DoDBs = []string{"test"}
   649  	c.Assert(cfg.Adjust(ctx), ErrorMatches, "invalid config: `mydumper\\.filter` and `black-white-list` cannot be simultaneously defined")
   650  
   651  	cfg.Mydumper.Filter = config.DefaultFilter
   652  	c.Assert(cfg.Adjust(ctx), IsNil)
   653  	c.Assert(cfg.HasLegacyBlackWhiteList(), IsTrue)
   654  }