github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/test/integration/checkout_int_test.go (about) 1 package integration 2 3 import ( 4 "bytes" 5 "fmt" 6 "os" 7 "path/filepath" 8 "runtime" 9 "strings" 10 "testing" 11 12 "github.com/ActiveState/cli/internal/constants" 13 "github.com/ActiveState/cli/internal/fileutils" 14 "github.com/ActiveState/cli/internal/osutils" 15 "github.com/ActiveState/cli/internal/testhelpers/e2e" 16 "github.com/ActiveState/cli/internal/testhelpers/suite" 17 "github.com/ActiveState/cli/internal/testhelpers/tagsuite" 18 "github.com/ActiveState/cli/pkg/platform/runtime/setup" 19 "github.com/ActiveState/cli/pkg/platform/runtime/target" 20 "github.com/ActiveState/cli/pkg/projectfile" 21 ) 22 23 type CheckoutIntegrationTestSuite struct { 24 tagsuite.Suite 25 } 26 27 func (suite *CheckoutIntegrationTestSuite) TestCheckoutPython() { 28 suite.OnlyRunForTags(tagsuite.Checkout) 29 30 ts := e2e.New(suite.T(), false) 31 defer ts.Close() 32 33 // Checkout and verify. 34 cp := ts.SpawnWithOpts( 35 e2e.OptArgs("checkout", "ActiveState-CLI/Python-3.9", "."), 36 e2e.OptAppendEnv(constants.DisableRuntime+"=false"), 37 ) 38 cp.Expect("Checking out project: ActiveState-CLI/Python-3.9") 39 cp.Expect("Setting up the following dependencies:") 40 cp.Expect("All dependencies have been installed and verified", e2e.RuntimeSourcingTimeoutOpt) 41 cp.Expect("Checked out project") 42 cp.ExpectExitCode(0) 43 suite.Require().True(fileutils.DirExists(ts.Dirs.Work), "state checkout should have created "+ts.Dirs.Work) 44 suite.Require().True(fileutils.FileExists(filepath.Join(ts.Dirs.Work, constants.ConfigFileName)), "ActiveState-CLI/Python3 was not checked out properly") 45 46 // Verify runtime was installed correctly and works. 47 targetDir := target.ProjectDirToTargetDir(ts.Dirs.Work, ts.Dirs.Cache) 48 pythonExe := filepath.Join(setup.ExecDir(targetDir), "python3"+osutils.ExeExtension) 49 cp = ts.SpawnCmd(pythonExe, "--version") 50 cp.Expect("Python 3") 51 cp.ExpectExitCode(0) 52 53 suite.Run("Cached", func() { 54 artifactCacheDir := filepath.Join(ts.Dirs.Cache, constants.ArtifactMetaDir) 55 projectCacheDir := target.ProjectDirToTargetDir(ts.Dirs.Work, ts.Dirs.Cache) 56 suite.Require().NotEmpty(fileutils.ListFilesUnsafe(artifactCacheDir), "Artifact cache dir should have files") 57 suite.Require().NotEmpty(fileutils.ListFilesUnsafe(projectCacheDir), "Project cache dir should have files") 58 59 suite.Require().NoError(os.RemoveAll(projectCacheDir)) // Ensure we can hit the cache by deleting the cache 60 suite.Require().NoError(os.Remove(filepath.Join(ts.Dirs.Work, constants.ConfigFileName))) // Ensure we can do another checkout 61 62 cp = ts.SpawnWithOpts( 63 e2e.OptArgs("checkout", "ActiveState-CLI/Python-3.9", "."), 64 e2e.OptAppendEnv( 65 constants.DisableRuntime+"=false", 66 "VERBOSE=true", // Necessary to assert "Fetched cached artifact" 67 ), 68 ) 69 cp.Expect("Fetched cached artifact", e2e.RuntimeSourcingTimeoutOpt) // Comes from log, which is why we're using VERBOSE=true 70 cp.Expect("Checked out project", e2e.RuntimeSourcingTimeoutOpt) 71 cp.ExpectExitCode(0) 72 }) 73 } 74 75 func (suite *CheckoutIntegrationTestSuite) TestCheckoutPerl() { 76 suite.OnlyRunForTags(tagsuite.Checkout, tagsuite.Critical) 77 78 ts := e2e.New(suite.T(), false) 79 defer ts.Close() 80 81 // Checkout and verify. 82 cp := ts.SpawnWithOpts( 83 e2e.OptArgs("checkout", "ActiveState-CLI/Perl-Alternative", "."), 84 e2e.OptAppendEnv(constants.DisableRuntime+"=false"), 85 ) 86 cp.Expect("Checking out project: ") 87 cp.Expect("Setting up the following dependencies:") 88 cp.Expect("All dependencies have been installed and verified", e2e.RuntimeSourcingTimeoutOpt) 89 cp.Expect("Checked out project") 90 91 // Verify runtime was installed correctly and works. 92 targetDir := target.ProjectDirToTargetDir(ts.Dirs.Work, ts.Dirs.Cache) 93 perlExe := filepath.Join(setup.ExecDir(targetDir), "perl"+osutils.ExeExtension) 94 cp = ts.SpawnCmd(perlExe, "--version") 95 cp.Expect("This is perl") 96 cp.ExpectExitCode(0) 97 } 98 99 func (suite *CheckoutIntegrationTestSuite) TestCheckoutNonEmptyDir() { 100 suite.OnlyRunForTags(tagsuite.Checkout) 101 102 ts := e2e.New(suite.T(), false) 103 defer ts.Close() 104 105 tmpdir := fileutils.TempDirUnsafe() 106 _, err := projectfile.Create(&projectfile.CreateParams{Owner: "foo", Project: "bar", Directory: tmpdir}) 107 suite.Require().NoError(err, "could not write project file") 108 _, err2 := fileutils.WriteTempFile("bogus.txt", []byte("test")) 109 suite.Require().NoError(err2, "could not write test file") 110 111 // Checkout and verify. 112 cp := ts.SpawnWithOpts( 113 e2e.OptArgs("checkout", "ActiveState-CLI/Python3", tmpdir), 114 e2e.OptAppendEnv(constants.DisableRuntime+"=false"), 115 ) 116 cp.Expect("already a project checked out at") 117 cp.ExpectExitCode(1) 118 ts.IgnoreLogErrors() 119 120 if strings.Count(cp.Snapshot(), " x ") != 1 { 121 suite.Fail("Expected exactly ONE error message, got: ", cp.Snapshot()) 122 } 123 124 // remove file 125 suite.Require().NoError(os.Remove(filepath.Join(tmpdir, constants.ConfigFileName))) 126 cp = ts.SpawnWithOpts( 127 e2e.OptArgs("checkout", "ActiveState-CLI/Python3", tmpdir), 128 e2e.OptAppendEnv(constants.DisableRuntime+"=false"), 129 ) 130 cp.Expect("Checked out project") 131 cp.ExpectExitCode(0) 132 } 133 134 func (suite *CheckoutIntegrationTestSuite) TestCheckoutMultiDir() { 135 suite.OnlyRunForTags(tagsuite.Checkout) 136 137 ts := e2e.New(suite.T(), false) 138 defer ts.Close() 139 140 dirs := []string{ 141 fileutils.TempDirUnsafe(), fileutils.TempDirUnsafe(), 142 } 143 144 for x, dir := range dirs { 145 cp := ts.SpawnWithOpts( 146 e2e.OptArgs("checkout", "ActiveState-CLI/Python3", "."), 147 e2e.OptWD(dir), 148 ) 149 cp.Expect("Skipping runtime setup") 150 cp.Expect("Checked out") 151 cp.ExpectExitCode(0) 152 suite.Require().FileExists(filepath.Join(dir, constants.ConfigFileName), "Dir %d", x) 153 } 154 } 155 156 func (suite *CheckoutIntegrationTestSuite) TestCheckoutWithFlags() { 157 suite.OnlyRunForTags(tagsuite.Checkout) 158 159 ts := e2e.New(suite.T(), false) 160 defer ts.Close() 161 162 // Test checking out to current working directory. 163 cp := ts.SpawnWithOpts(e2e.OptArgs("checkout", "ActiveState-CLI/Python3", ".")) 164 cp.Expect("Skipping runtime setup") 165 cp.Expect("Checked out") 166 cp.Expect(ts.Dirs.Work) 167 suite.Assert().True(fileutils.FileExists(filepath.Join(ts.Dirs.Work, constants.ConfigFileName)), "ActiveState-CLI/Python3 was not checked out to the current working directory") 168 169 // Test checkout out to a generic path. 170 python3Dir := filepath.Join(ts.Dirs.Work, "MyPython3") 171 cp = ts.SpawnWithOpts(e2e.OptArgs("checkout", "ActiveState-CLI/Python3#6d9280e7-75eb-401a-9e71-0d99759fbad3", python3Dir)) 172 cp.Expect("Skipping runtime setup") 173 cp.Expect("Checked out") 174 cp.ExpectExitCode(0) 175 176 suite.Require().True(fileutils.DirExists(python3Dir), "state checkout should have created "+python3Dir) 177 asy := filepath.Join(python3Dir, constants.ConfigFileName) 178 suite.Require().True(fileutils.FileExists(asy), "ActiveState-CLI/Python3 was not checked out properly") 179 suite.Assert().True(bytes.Contains(fileutils.ReadFileUnsafe(asy), []byte("6d9280e7-75eb-401a-9e71-0d99759fbad3")), "did not check out specific commit ID") 180 181 // Test --branch mismatch in non-checked-out project. 182 branchPath := filepath.Join(ts.Dirs.Base, "branch") 183 cp = ts.SpawnWithOpts(e2e.OptArgs("checkout", "ActiveState-CLI/Python-3.9", branchPath, "--branch", "doesNotExist")) 184 cp.Expect("This project has no branch with label matching 'doesNotExist'") 185 cp.ExpectExitCode(1) 186 ts.IgnoreLogErrors() 187 } 188 189 func (suite *CheckoutIntegrationTestSuite) TestCheckoutCustomRTPath() { 190 suite.OnlyRunForTags(tagsuite.Checkout) 191 192 ts := e2e.New(suite.T(), true) 193 defer ts.Close() 194 195 customRTPath, err := fileutils.ResolveUniquePath(filepath.Join(ts.Dirs.Work, "custom-cache")) 196 suite.Require().NoError(err) 197 err = fileutils.Mkdir(customRTPath) 198 suite.Require().NoError(err) 199 200 // Checkout and verify. 201 cp := ts.SpawnWithOpts( 202 e2e.OptArgs("checkout", "ActiveState-CLI/Python3", fmt.Sprintf("--runtime-path=%s", customRTPath)), 203 e2e.OptAppendEnv(constants.DisableRuntime+"=false"), 204 ) 205 cp.Expect("Checked out project", e2e.RuntimeSourcingTimeoutOpt) 206 207 pythonExe := filepath.Join(setup.ExecDir(customRTPath), "python3"+osutils.ExeExtension) 208 suite.Require().True(fileutils.DirExists(customRTPath)) 209 suite.Require().True(fileutils.FileExists(pythonExe)) 210 211 // Verify runtime was installed correctly and works. 212 cp = ts.SpawnCmd(pythonExe, "--version") 213 cp.Expect("Python 3") 214 cp.ExpectExitCode(0) 215 216 // Verify that state exec works with custom cache. 217 cp = ts.SpawnWithOpts( 218 e2e.OptArgs("exec", "python3", "--", "-c", "import sys;print(sys.executable)"), 219 e2e.OptAppendEnv(constants.DisableRuntime+"=false"), 220 e2e.OptWD(filepath.Join(ts.Dirs.Work, "Python3")), 221 ) 222 if runtime.GOOS == "windows" { 223 customRTPath, err = fileutils.GetLongPathName(customRTPath) 224 suite.Require().NoError(err) 225 customRTPath = strings.ToUpper(customRTPath[:1]) + strings.ToLower(customRTPath[1:]) // capitalize drive letter 226 } 227 cp.Expect(customRTPath, e2e.RuntimeSourcingTimeoutOpt) 228 } 229 230 func (suite *CheckoutIntegrationTestSuite) TestCheckoutNotFound() { 231 suite.OnlyRunForTags(tagsuite.Checkout) 232 233 ts := e2e.New(suite.T(), false) 234 defer ts.Close() 235 236 cp := ts.SpawnWithOpts(e2e.OptArgs("checkout", "ActiveState-CLI/Bogus-Project-That-Doesnt-Exist")) 237 cp.Expect("does not exist under") // error 238 cp.Expect("If this is a private project") // tip 239 cp.ExpectExitCode(1) 240 ts.IgnoreLogErrors() 241 242 if strings.Count(cp.Snapshot(), " x ") != 1 { 243 suite.Fail("Expected exactly ONE error message, got: ", cp.Snapshot()) 244 } 245 } 246 247 func (suite *CheckoutIntegrationTestSuite) TestCheckoutAlreadyCheckedOut() { 248 suite.OnlyRunForTags(tagsuite.Checkout) 249 250 ts := e2e.New(suite.T(), false) 251 defer ts.Close() 252 253 cp := ts.SpawnWithOpts(e2e.OptArgs("checkout", "ActiveState-CLI/small-python")) 254 cp.Expect("Checked out project") 255 cp.ExpectExitCode(0) 256 257 cp = ts.SpawnWithOpts(e2e.OptArgs("checkout", "ActiveState-CLI/small-python")) 258 cp.Expect("already a project checked out at") 259 cp.ExpectNotExitCode(0) 260 ts.IgnoreLogErrors() 261 } 262 263 func (suite *CheckoutIntegrationTestSuite) TestJSON() { 264 suite.OnlyRunForTags(tagsuite.Checkout, tagsuite.JSON) 265 ts := e2e.New(suite.T(), false) 266 defer ts.Close() 267 268 cp := ts.SpawnWithOpts(e2e.OptArgs("checkout", "ActiveState-CLI/small-python", "-o", "json")) 269 cp.ExpectExitCode(0) 270 AssertValidJSON(suite.T(), cp) 271 272 cp = ts.SpawnWithOpts(e2e.OptArgs("checkout", "ActiveState-CLI/Bogus-Project-That-Doesnt-Exist", "-o", "json")) 273 cp.Expect("does not exist") // error 274 cp.Expect(`"tips":["If this is a private project`) // tip 275 cp.ExpectNotExitCode(0) 276 AssertValidJSON(suite.T(), cp) 277 ts.IgnoreLogErrors() 278 } 279 280 func (suite *CheckoutIntegrationTestSuite) TestCheckoutCaseInsensitive() { 281 suite.OnlyRunForTags(tagsuite.Checkout) 282 283 ts := e2e.New(suite.T(), false) 284 defer ts.Close() 285 286 cp := ts.SpawnWithOpts(e2e.OptArgs("checkout", "ACTIVESTATE-CLI/SMALL-PYTHON")) 287 cp.Expect("Skipping runtime setup") 288 cp.Expect("Checked out project") 289 cp.ExpectExitCode(0) 290 291 bytes := fileutils.ReadFileUnsafe(filepath.Join(ts.Dirs.Work, "SMALL-PYTHON", constants.ConfigFileName)) 292 suite.Assert().Contains(string(bytes), "ActiveState-CLI/small-python", "did not match namespace case") 293 suite.Assert().NotContains(string(bytes), "ACTIVESTATE-CLI/SMALL-PYTHON", "kept incorrect namespace case") 294 } 295 296 func (suite *CheckoutIntegrationTestSuite) TestCheckoutBuildtimeClosure() { 297 if runtime.GOOS == "windows" { 298 suite.T().Skip("Skipping on windows since the build time is different there, and testing it on mac/linux is sufficient") 299 return 300 } 301 suite.OnlyRunForTags(tagsuite.Checkout) 302 303 ts := e2e.New(suite.T(), false) 304 defer ts.Close() 305 306 cp := ts.SpawnWithOpts( 307 e2e.OptArgs("checkout", "ActiveState-CLI/small-python#5a1e49e5-8ceb-4a09-b605-ed334474855b"), 308 e2e.OptAppendEnv(constants.InstallBuildDependencies+"=true"), 309 e2e.OptAppendEnv(constants.DisableRuntime+"=false"), 310 ) 311 // Expect the number of build deps to be 27 which is more than the number of runtime deps. 312 // Also expect ncurses which should not be in the runtime closure. 313 cp.Expect("ncurses", e2e.RuntimeSourcingTimeoutOpt) 314 cp.Expect("27/27", e2e.RuntimeSourcingTimeoutOpt) 315 cp.ExpectExitCode(0, e2e.RuntimeSourcingTimeoutOpt) 316 } 317 318 func (suite *CheckoutIntegrationTestSuite) TestFail() { 319 suite.OnlyRunForTags(tagsuite.Checkout) 320 ts := e2e.New(suite.T(), false) 321 defer ts.Close() 322 323 cp := ts.SpawnWithOpts( 324 e2e.OptArgs("checkout", "ActiveState-CLI/fail"), 325 e2e.OptAppendEnv(constants.DisableRuntime+"=false"), 326 ) 327 cp.Expect("failed to build") 328 cp.ExpectNotExitCode(0) 329 suite.Assert().NoDirExists(filepath.Join(ts.Dirs.Work, "fail"), "state checkout fail did not remove created directory") 330 ts.IgnoreLogErrors() 331 332 cp = ts.SpawnWithOpts( 333 e2e.OptArgs("checkout", "ActiveState-CLI/fail", "."), 334 e2e.OptAppendEnv(constants.DisableRuntime+"=false"), 335 ) 336 cp.Expect("failed to build") 337 cp.ExpectNotExitCode(0) 338 suite.Assert().NoFileExists(filepath.Join(ts.Dirs.Work, constants.ConfigFileName), "state checkout fail did not remove created activestate.yaml") 339 340 cp = ts.SpawnWithOpts( 341 e2e.OptArgs("checkout", "ActiveState-CLI/fail", "--force"), 342 e2e.OptAppendEnv(constants.DisableRuntime+"=false"), 343 ) 344 cp.Expect("failed to build") 345 cp.ExpectNotExitCode(0) 346 suite.Assert().DirExists(filepath.Join(ts.Dirs.Work, "fail"), "state checkout fail did not leave created directory there despite --force flag override") 347 } 348 349 func (suite *CheckoutIntegrationTestSuite) TestBranch() { 350 suite.OnlyRunForTags(tagsuite.Checkout) 351 ts := e2e.New(suite.T(), false) 352 defer ts.Close() 353 354 cp := ts.Spawn("checkout", "ActiveState-CLI/Branches", "--branch", "firstbranch", ".") 355 cp.Expect("Skipping runtime setup") 356 cp.Expect("Checked out") 357 cp.ExpectExitCode(0) 358 359 asy := filepath.Join(ts.Dirs.Work, constants.ConfigFileName) 360 suite.Assert().Contains(string(fileutils.ReadFileUnsafe(asy)), "branch=firstbranch", "activestate.yaml does not have correct branch") 361 362 suite.Require().NoError(os.Remove(asy)) 363 364 // Infer branch name from commit. 365 cp = ts.Spawn("checkout", "ActiveState-CLI/Branches#46c83477-d580-43e2-a0c6-f5d3677517f1", ".") 366 cp.Expect("Skipping runtime setup") 367 cp.Expect("Checked out") 368 cp.ExpectExitCode(0) 369 370 suite.Assert().Contains(string(fileutils.ReadFileUnsafe(asy)), "branch=secondbranch", "activestate.yaml does not have correct branch") 371 } 372 373 func TestCheckoutIntegrationTestSuite(t *testing.T) { 374 suite.Run(t, new(CheckoutIntegrationTestSuite)) 375 }