github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/dtestutils/testcommands/multienv.go (about)

     1  // Copyright 2020 Dolthub, 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  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package testcommands
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"os"
    21  	"path/filepath"
    22  
    23  	cmd "github.com/dolthub/dolt/go/cmd/dolt/commands"
    24  	"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dprocedures"
    25  	"github.com/dolthub/dolt/go/libraries/utils/config"
    26  
    27  	"github.com/dolthub/dolt/go/cmd/dolt/cli"
    28  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
    29  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb/durable"
    30  	"github.com/dolthub/dolt/go/libraries/doltcore/dtestutils"
    31  	"github.com/dolthub/dolt/go/libraries/doltcore/env"
    32  	"github.com/dolthub/dolt/go/libraries/doltcore/env/actions"
    33  	"github.com/dolthub/dolt/go/libraries/doltcore/row"
    34  	"github.com/dolthub/dolt/go/libraries/doltcore/schema"
    35  	"github.com/dolthub/dolt/go/libraries/doltcore/table"
    36  	"github.com/dolthub/dolt/go/libraries/utils/filesys"
    37  	"github.com/dolthub/dolt/go/store/datas"
    38  	"github.com/dolthub/dolt/go/store/types"
    39  )
    40  
    41  const (
    42  	homePrefix = "home"
    43  )
    44  
    45  type MultiRepoTestSetup struct {
    46  	envs    map[string]*env.DoltEnv
    47  	Remote  string
    48  	DoltDBs map[string]*doltdb.DoltDB
    49  	DbNames []string
    50  	Root    string
    51  	DbPaths map[string]string
    52  	Home    string
    53  	Remotes map[string]env.Remote
    54  	Errhand func(args ...interface{})
    55  }
    56  
    57  const (
    58  	name          = "billy bob"
    59  	email         = "bigbillieb@fake.horse"
    60  	defaultBranch = "main"
    61  )
    62  
    63  func NewMultiRepoTestSetup(errhand func(args ...interface{})) *MultiRepoTestSetup {
    64  	dir, err := os.MkdirTemp("", "")
    65  	if err != nil {
    66  		errhand(err)
    67  	}
    68  
    69  	homeDir, err := os.MkdirTemp(dir, homePrefix)
    70  	if err != nil {
    71  		errhand(err)
    72  	}
    73  
    74  	return &MultiRepoTestSetup{
    75  		envs:    make(map[string]*env.DoltEnv),
    76  		Remotes: make(map[string]env.Remote),
    77  		DoltDBs: make(map[string]*doltdb.DoltDB, 0),
    78  		DbNames: make([]string, 0),
    79  		Root:    dir,
    80  		Home:    homeDir,
    81  		DbPaths: make(map[string]string, 0),
    82  		Errhand: errhand,
    83  	}
    84  }
    85  
    86  func (mr *MultiRepoTestSetup) GetEnv(dbName string) *env.DoltEnv {
    87  	return mr.envs[dbName]
    88  }
    89  
    90  func (mr *MultiRepoTestSetup) homeProv() (string, error) {
    91  	return mr.Home, nil
    92  }
    93  
    94  func (mr *MultiRepoTestSetup) Close() {
    95  	for _, db := range mr.DoltDBs {
    96  		err := db.Close()
    97  		if err != nil {
    98  			mr.Errhand(err)
    99  		}
   100  	}
   101  }
   102  
   103  func (mr *MultiRepoTestSetup) Cleanup(dbName string) {
   104  	os.RemoveAll(mr.Root)
   105  }
   106  
   107  func (mr *MultiRepoTestSetup) NewDB(dbName string) {
   108  	ctx := context.Background()
   109  
   110  	repo := filepath.Join(mr.Root, dbName)
   111  	os.Mkdir(repo, os.ModePerm)
   112  
   113  	err := os.Chdir(repo)
   114  	if err != nil {
   115  		mr.Errhand(err)
   116  	}
   117  
   118  	// TODO sometimes tempfiles scrubber is racy with tempfolder deleter
   119  	dEnv := env.Load(context.Background(), mr.homeProv, filesys.LocalFS, doltdb.LocalDirDoltDB, "test")
   120  	if err != nil {
   121  		mr.Errhand("Failed to initialize environment:" + err.Error())
   122  	}
   123  	cfg, _ := dEnv.Config.GetConfig(env.GlobalConfig)
   124  	cfg.SetStrings(map[string]string{
   125  		config.UserNameKey:  name,
   126  		config.UserEmailKey: email,
   127  	})
   128  	err = dEnv.InitRepo(context.Background(), types.Format_Default, name, email, defaultBranch)
   129  	if err != nil {
   130  		mr.Errhand("Failed to initialize environment:" + err.Error())
   131  	}
   132  
   133  	ddb, err := doltdb.LoadDoltDB(ctx, types.Format_Default, doltdb.LocalDirDoltDB, filesys.LocalFS)
   134  	if err != nil {
   135  		mr.Errhand("Failed to initialize environment:" + err.Error())
   136  	}
   137  
   138  	dEnv = env.Load(context.Background(), mr.homeProv, filesys.LocalFS, doltdb.LocalDirDoltDB, "test")
   139  
   140  	mr.envs[dbName] = dEnv
   141  	mr.DoltDBs[dbName] = ddb
   142  	mr.DbNames = append(mr.DbNames, dbName)
   143  	mr.DbPaths[dbName] = repo
   144  }
   145  
   146  func (mr *MultiRepoTestSetup) NewRemote(remoteName string) {
   147  	remote := filepath.Join(mr.Root, remoteName)
   148  	os.Mkdir(remote, os.ModePerm)
   149  	remotePath := fmt.Sprintf("file:///%s", remote)
   150  
   151  	rem := env.NewRemote(remoteName, remotePath, nil)
   152  
   153  	for _, dEnv := range mr.envs {
   154  		dEnv.RepoState.AddRemote(rem)
   155  		dEnv.RepoState.Save(filesys.LocalFS)
   156  	}
   157  
   158  	mr.Remotes[remoteName] = rem
   159  }
   160  
   161  func (mr *MultiRepoTestSetup) NewBranch(dbName, branchName string) {
   162  	dEnv := mr.envs[dbName]
   163  	err := actions.CreateBranchWithStartPt(context.Background(), dEnv.DbData(), branchName, "head", false, nil)
   164  	if err != nil {
   165  		mr.Errhand(err)
   166  	}
   167  }
   168  
   169  func (mr *MultiRepoTestSetup) CheckoutBranch(dbName, branchName string) {
   170  	dEnv := mr.envs[dbName]
   171  	cliCtx, _ := cmd.NewArgFreeCliContext(context.Background(), dEnv)
   172  	_, sqlCtx, closeFunc, err := cliCtx.QueryEngine(context.Background())
   173  	if err != nil {
   174  		mr.Errhand(err)
   175  	}
   176  	defer closeFunc()
   177  	err = dprocedures.MoveWorkingSetToBranch(sqlCtx, branchName, false, false)
   178  	if err != nil {
   179  		mr.Errhand(err)
   180  	}
   181  }
   182  
   183  func (mr *MultiRepoTestSetup) CloneDB(fromRemote, dbName string) {
   184  	ctx := context.Background()
   185  	cloneDir := filepath.Join(mr.Root, dbName)
   186  
   187  	r := mr.GetRemote(fromRemote)
   188  	srcDB, err := r.GetRemoteDB(ctx, types.Format_Default, mr.envs[dbName])
   189  	if err != nil {
   190  		mr.Errhand(err)
   191  	}
   192  
   193  	dEnv := env.Load(context.Background(), mr.homeProv, filesys.LocalFS, doltdb.LocalDirDoltDB, "test")
   194  	dEnv, err = actions.EnvForClone(ctx, srcDB.Format(), r, cloneDir, dEnv.FS, dEnv.Version, mr.homeProv)
   195  	if err != nil {
   196  		mr.Errhand(err)
   197  	}
   198  
   199  	err = actions.CloneRemote(ctx, srcDB, r.Name, "", false, -1, dEnv)
   200  	if err != nil {
   201  		mr.Errhand(err)
   202  	}
   203  
   204  	wd, err := os.Getwd()
   205  	if err != nil {
   206  		mr.Errhand(err)
   207  	}
   208  	err = os.Chdir(cloneDir)
   209  	if err != nil {
   210  		mr.Errhand(err)
   211  	}
   212  	defer os.Chdir(wd)
   213  
   214  	ddb := dEnv.DoltDB
   215  
   216  	mr.envs[dbName] = dEnv
   217  	mr.DoltDBs[dbName] = ddb
   218  	mr.DbNames = append(mr.DbNames, dbName)
   219  	mr.DbPaths[dbName] = cloneDir
   220  }
   221  
   222  func (mr *MultiRepoTestSetup) GetRemote(remoteName string) env.Remote {
   223  	rem, ok := mr.Remotes[remoteName]
   224  	if !ok {
   225  		mr.Errhand("remote not found")
   226  	}
   227  	return rem
   228  }
   229  
   230  func (mr *MultiRepoTestSetup) GetDB(dbName string) *doltdb.DoltDB {
   231  	db, ok := mr.DoltDBs[dbName]
   232  	if !ok {
   233  		mr.Errhand("db not found")
   234  	}
   235  	return db
   236  }
   237  
   238  func (mr *MultiRepoTestSetup) CommitWithWorkingSet(dbName string) *doltdb.Commit {
   239  	ctx := context.Background()
   240  	dEnv := mr.envs[dbName]
   241  	ws, err := dEnv.WorkingSet(ctx)
   242  	if err != nil {
   243  		panic("couldn't get working set: " + err.Error())
   244  	}
   245  
   246  	prevHash, err := ws.HashOf()
   247  	if err != nil {
   248  		panic("couldn't get working set: " + err.Error())
   249  	}
   250  
   251  	var mergeParentCommits []*doltdb.Commit
   252  	if ws.MergeActive() {
   253  		mergeParentCommits = []*doltdb.Commit{ws.MergeState().Commit()}
   254  	}
   255  
   256  	t := datas.CommitterDate()
   257  	roots, err := dEnv.Roots(ctx)
   258  	if err != nil {
   259  		panic("couldn't get roots: " + err.Error())
   260  	}
   261  	pendingCommit, err := actions.GetCommitStaged(ctx, roots, ws, mergeParentCommits, dEnv.DbData().Ddb, actions.CommitStagedProps{
   262  		Message:    "auto commit",
   263  		Date:       t,
   264  		AllowEmpty: true,
   265  		Force:      false,
   266  		Name:       name,
   267  		Email:      email,
   268  	})
   269  	if err != nil {
   270  		panic("pending commit error: " + err.Error())
   271  	}
   272  
   273  	headRef, err := dEnv.RepoStateReader().CWBHeadRef()
   274  	if err != nil {
   275  		panic("couldn't get working set: " + err.Error())
   276  	}
   277  
   278  	commit, err := dEnv.DoltDB.CommitWithWorkingSet(
   279  		ctx,
   280  		headRef,
   281  		ws.Ref(),
   282  		pendingCommit,
   283  		ws.WithStagedRoot(pendingCommit.Roots.Staged).WithWorkingRoot(pendingCommit.Roots.Working).ClearMerge(),
   284  		prevHash,
   285  		doltdb.TodoWorkingSetMeta(),
   286  		nil,
   287  	)
   288  	if err != nil {
   289  		panic("couldn't commit: " + err.Error())
   290  	}
   291  	return commit
   292  }
   293  
   294  func createTestDataTable(ctx context.Context, ddb *doltdb.DoltDB) (*table.InMemTable, schema.Schema) {
   295  	rows, sch, err := dtestutils.RowsAndSchema()
   296  	if err != nil {
   297  		panic(err)
   298  	}
   299  
   300  	imt := table.NewInMemTable(sch)
   301  
   302  	for _, r := range rows {
   303  		err := imt.AppendRow(ctx, ddb.ValueReadWriter(), r)
   304  		if err != nil {
   305  			panic(err)
   306  		}
   307  	}
   308  
   309  	return imt, sch
   310  }
   311  
   312  func (mr *MultiRepoTestSetup) CreateTable(ctx context.Context, dbName, tblName string) {
   313  	dEnv := mr.envs[dbName]
   314  
   315  	imt, sch := createTestDataTable(ctx, dEnv.DoltDB)
   316  	rows := make([]row.Row, imt.NumRows())
   317  	for i := 0; i < imt.NumRows(); i++ {
   318  		r, err := imt.GetRow(i)
   319  		if err != nil {
   320  			mr.Errhand(fmt.Sprintf("Failed to create table: %s", err.Error()))
   321  		}
   322  		rows[i] = r
   323  	}
   324  	if err := createTestTable(dEnv, tblName, sch); err != nil {
   325  		mr.Errhand(err)
   326  	}
   327  }
   328  
   329  func (mr *MultiRepoTestSetup) StageAll(dbName string) {
   330  	dEnv := mr.envs[dbName]
   331  
   332  	ctx := context.Background()
   333  	roots, err := dEnv.Roots(ctx)
   334  	if err != nil {
   335  		mr.Errhand(fmt.Sprintf("Failed to get roots: %s", dbName))
   336  	}
   337  
   338  	roots, err = actions.StageAllTables(ctx, roots, true)
   339  	if err != nil {
   340  		mr.Errhand(fmt.Sprintf("Failed to stage tables: %s", dbName))
   341  	}
   342  	err = dEnv.UpdateRoots(ctx, roots)
   343  	if err != nil {
   344  		mr.Errhand(fmt.Sprintf("Failed to update roots: %s", dbName))
   345  	}
   346  }
   347  
   348  func (mr *MultiRepoTestSetup) PushToRemote(dbName, remoteName, branchName string) {
   349  	ctx := context.Background()
   350  	dEnv := mr.envs[dbName]
   351  
   352  	ap := cli.CreatePushArgParser()
   353  	apr, err := ap.Parse([]string{remoteName, branchName})
   354  	if err != nil {
   355  		mr.Errhand(fmt.Sprintf("Failed to push remote: %s", err.Error()))
   356  	}
   357  	targets, remote, err := env.NewPushOpts(ctx, apr, dEnv.RepoStateReader(), dEnv.DoltDB, false, false, false, false)
   358  	if err != nil {
   359  		mr.Errhand(fmt.Sprintf("Failed to push remote: %s", err.Error()))
   360  	}
   361  
   362  	remoteDB, err := remote.GetRemoteDB(ctx, dEnv.DoltDB.ValueReadWriter().Format(), mr.envs[dbName])
   363  	if err != nil {
   364  		mr.Errhand(actions.HandleInitRemoteStorageClientErr(remote.Name, remote.Url, err))
   365  	}
   366  
   367  	tmpDir, err := dEnv.TempTableFilesDir()
   368  	if err != nil {
   369  		mr.Errhand(fmt.Sprintf("Failed to access .dolt directory: %s", err.Error()))
   370  	}
   371  
   372  	pushOptions := &env.PushOptions{
   373  		Targets: targets,
   374  		Remote:  remote,
   375  		Rsr:     dEnv.RepoStateReader(),
   376  		Rsw:     dEnv.RepoStateWriter(),
   377  		SrcDb:   dEnv.DoltDB,
   378  		DestDb:  remoteDB,
   379  		TmpDir:  tmpDir,
   380  	}
   381  	_, err = actions.DoPush(ctx, pushOptions, actions.NoopRunProgFuncs, actions.NoopStopProgFuncs)
   382  	if err != nil {
   383  		mr.Errhand(fmt.Sprintf("Failed to push remote: %s", err.Error()))
   384  	}
   385  }
   386  
   387  // createTestTable creates a new test table with the name, schema, and rows given.
   388  func createTestTable(dEnv *env.DoltEnv, tableName string, sch schema.Schema) error {
   389  	ctx := context.Background()
   390  	vrw := dEnv.DoltDB.ValueReadWriter()
   391  	ns := dEnv.DoltDB.NodeStore()
   392  
   393  	idx, err := durable.NewEmptyIndex(ctx, vrw, ns, sch)
   394  	if err != nil {
   395  		return err
   396  	}
   397  
   398  	tbl, err := doltdb.NewTable(ctx, vrw, ns, sch, idx, nil, nil)
   399  	if err != nil {
   400  		return err
   401  	}
   402  
   403  	sch, err = tbl.GetSchema(ctx)
   404  	if err != nil {
   405  		return err
   406  	}
   407  
   408  	root, err := dEnv.WorkingRoot(ctx)
   409  	if err != nil {
   410  		return fmt.Errorf("%w: %v", doltdb.ErrNomsIO, err)
   411  	}
   412  
   413  	newRoot, err := root.PutTable(ctx, doltdb.TableName{Name: tableName}, tbl)
   414  	if err != nil {
   415  		return err
   416  	}
   417  
   418  	rootHash, err := root.HashOf()
   419  	if err != nil {
   420  		return err
   421  	}
   422  
   423  	newRootHash, err := newRoot.HashOf()
   424  	if err != nil {
   425  		return err
   426  	}
   427  	if rootHash == newRootHash {
   428  		return nil
   429  	}
   430  	return dEnv.UpdateWorkingRoot(ctx, newRoot)
   431  }