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 }