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

     1  // Copyright 2021 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 conn
    15  
    16  import (
    17  	"database/sql"
    18  	"fmt"
    19  	"io"
    20  	"net"
    21  	"net/http"
    22  	"time"
    23  
    24  	"github.com/DATA-DOG/go-sqlmock"
    25  	check "github.com/pingcap/check"
    26  	tidbConfig "github.com/pingcap/tidb/pkg/config"
    27  	"github.com/pingcap/tidb/pkg/domain"
    28  	"github.com/pingcap/tidb/pkg/kv"
    29  	"github.com/pingcap/tidb/pkg/server"
    30  	"github.com/pingcap/tidb/pkg/session"
    31  	"github.com/pingcap/tidb/pkg/store/mockstore"
    32  	"github.com/tikv/client-go/v2/testutils"
    33  )
    34  
    35  type mockDBProvider struct {
    36  	verDB *sql.DB // verDB user for show version.
    37  	db    *sql.DB
    38  	// customDB defines a db that will never be close
    39  	// TODO: we should use customDB for all mock.
    40  	customDB *sql.DB
    41  }
    42  
    43  // Apply will build BaseDB with DBConfig.
    44  func (d *mockDBProvider) Apply(config ScopedDBConfig) (*BaseDB, error) {
    45  	if d.verDB != nil {
    46  		if err := d.verDB.Ping(); err == nil {
    47  			// nolint:nilerr
    48  			return NewBaseDBForTest(d.verDB), nil
    49  		}
    50  	}
    51  	if d.customDB != nil {
    52  		if err := d.customDB.Ping(); err == nil {
    53  			// nolint:nilerr
    54  			return NewMockDB(d.customDB), nil
    55  		}
    56  	}
    57  	return NewBaseDBForTest(d.db), nil
    58  }
    59  
    60  // InitMockDB return a mocked db for unit test.
    61  func InitMockDB(c *check.C) sqlmock.Sqlmock {
    62  	db, mock, err := sqlmock.New()
    63  	c.Assert(err, check.IsNil)
    64  	if mdbp, ok := DefaultDBProvider.(*mockDBProvider); ok {
    65  		mdbp.db = db
    66  	} else {
    67  		DefaultDBProvider = &mockDBProvider{db: db}
    68  	}
    69  	return mock
    70  }
    71  
    72  // MockDefaultDBProvider return a mocked db for unit test.
    73  func MockDefaultDBProvider() (sqlmock.Sqlmock, error) {
    74  	db, mock, err := sqlmock.New()
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	if mdbp, ok := DefaultDBProvider.(*mockDBProvider); ok {
    79  		mdbp.db = db
    80  	} else {
    81  		DefaultDBProvider = &mockDBProvider{db: db}
    82  	}
    83  	return mock, nil
    84  }
    85  
    86  // InitVersionDB return a mocked db for unit test's show version.
    87  func InitVersionDB() sqlmock.Sqlmock {
    88  	// nolint:errcheck
    89  	db, mock, _ := sqlmock.New()
    90  	if mdbp, ok := DefaultDBProvider.(*mockDBProvider); ok {
    91  		mdbp.verDB = db
    92  	} else {
    93  		DefaultDBProvider = &mockDBProvider{verDB: db}
    94  	}
    95  	return mock
    96  }
    97  
    98  func InitMockDBFull() (*sql.DB, sqlmock.Sqlmock, error) {
    99  	db, mock, err := sqlmock.New()
   100  	if err != nil {
   101  		return nil, nil, err
   102  	}
   103  	if mdbp, ok := DefaultDBProvider.(*mockDBProvider); ok {
   104  		mdbp.db = db
   105  	} else {
   106  		DefaultDBProvider = &mockDBProvider{db: db}
   107  	}
   108  	return db, mock, err
   109  }
   110  
   111  func InitMockDBNotClose() (*sql.DB, sqlmock.Sqlmock, error) {
   112  	db, mock, err := sqlmock.New()
   113  	if err != nil {
   114  		return nil, nil, err
   115  	}
   116  	if mdbp, ok := DefaultDBProvider.(*mockDBProvider); ok {
   117  		mdbp.customDB = db
   118  	} else {
   119  		DefaultDBProvider = &mockDBProvider{customDB: db}
   120  	}
   121  	return db, mock, err
   122  }
   123  
   124  // TODO: export Config in https://github.com/pingcap/tidb/blob/a8fa29b56d633b1ec843e21cb89131dd4fd601db/br/pkg/mock/mock_cluster.go#L35
   125  // Cluster is mock tidb cluster.
   126  type Cluster struct {
   127  	*server.Server
   128  	testutils.Cluster
   129  	kv.Storage
   130  	*server.TiDBDriver
   131  	*domain.Domain
   132  	Port int
   133  }
   134  
   135  // NewCluster create a new mock cluster.
   136  func NewCluster() (*Cluster, error) {
   137  	cluster := &Cluster{}
   138  
   139  	storage, err := mockstore.NewMockStore(
   140  		mockstore.WithClusterInspector(func(c testutils.Cluster) {
   141  			mockstore.BootstrapWithSingleStore(c)
   142  			cluster.Cluster = c
   143  		}),
   144  	)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  	cluster.Storage = storage
   149  
   150  	session.SetSchemaLease(0)
   151  	session.DisableStats4Test()
   152  	dom, err := session.BootstrapSession(storage)
   153  	if err != nil {
   154  		return nil, err
   155  	}
   156  	cluster.Domain = dom
   157  
   158  	return cluster, nil
   159  }
   160  
   161  // Start runs a mock cluster.
   162  func (mock *Cluster) Start() error {
   163  	// choose a random available port
   164  	l1, _ := net.Listen("tcp", "127.0.0.1:")
   165  	statusPort := l1.Addr().(*net.TCPAddr).Port
   166  
   167  	// choose a random available port
   168  	l2, _ := net.Listen("tcp", "127.0.0.1:")
   169  	addrPort := l2.Addr().(*net.TCPAddr).Port
   170  
   171  	mock.TiDBDriver = server.NewTiDBDriver(mock.Storage)
   172  	cfg := tidbConfig.NewConfig()
   173  	cfg.Port = uint(addrPort)
   174  	cfg.Store = "tikv"
   175  	cfg.Status.StatusPort = uint(statusPort)
   176  	cfg.Status.ReportStatus = true
   177  	cfg.Socket = fmt.Sprintf("/tmp/tidb-mock-%d.sock", time.Now().UnixNano())
   178  
   179  	// close port for next listen in NewServer
   180  	l1.Close()
   181  	l2.Close()
   182  	svr, err := server.NewServer(cfg, mock.TiDBDriver)
   183  	if err != nil {
   184  		return err
   185  	}
   186  	mock.Server = svr
   187  	mock.Server.SetDomain(mock.Domain)
   188  	go func() {
   189  		if err1 := svr.Run(nil); err1 != nil {
   190  			panic(err1)
   191  		}
   192  	}()
   193  	waitUntilServerOnline(cfg.Status.StatusPort)
   194  	mock.Port = addrPort
   195  	return nil
   196  }
   197  
   198  // Stop stops a mock cluster.
   199  func (mock *Cluster) Stop() {
   200  	if mock.Domain != nil {
   201  		mock.Domain.Close()
   202  	}
   203  	if mock.Storage != nil {
   204  		_ = mock.Storage.Close()
   205  	}
   206  	if mock.Server != nil {
   207  		mock.Server.Close()
   208  	}
   209  }
   210  
   211  func waitUntilServerOnline(statusPort uint) {
   212  	// connect http status
   213  	statusURL := fmt.Sprintf("http://127.0.0.1:%d/status", statusPort)
   214  	for retry := 0; retry < 100; retry++ {
   215  		// nolint:gosec,noctx
   216  		// #nosec G107
   217  		resp, err := http.Get(statusURL)
   218  		if err == nil {
   219  			// Ignore errors.
   220  			_, _ = io.ReadAll(resp.Body)
   221  			_ = resp.Body.Close()
   222  			break
   223  		}
   224  		time.Sleep(time.Millisecond * 10)
   225  	}
   226  }