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  }