github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/test/integration/activate_int_test.go (about)

     1  package integration
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"os/exec"
     8  	"path/filepath"
     9  	"regexp"
    10  	"runtime"
    11  	"strings"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/ActiveState/cli/internal/testhelpers/suite"
    16  	"github.com/ActiveState/termtest"
    17  
    18  	"github.com/ActiveState/cli/internal/rtutils"
    19  
    20  	"github.com/ActiveState/cli/internal/constants"
    21  	"github.com/ActiveState/cli/internal/fileutils"
    22  	"github.com/ActiveState/cli/internal/osutils"
    23  	"github.com/ActiveState/cli/internal/testhelpers/e2e"
    24  	"github.com/ActiveState/cli/internal/testhelpers/tagsuite"
    25  )
    26  
    27  type ActivateIntegrationTestSuite struct {
    28  	tagsuite.Suite
    29  }
    30  
    31  func (suite *ActivateIntegrationTestSuite) TestActivatePython3() {
    32  	suite.OnlyRunForTags(tagsuite.Python, tagsuite.Activate, tagsuite.Critical)
    33  	suite.activatePython("3")
    34  }
    35  
    36  func (suite *ActivateIntegrationTestSuite) TestActivatePython3_zsh() {
    37  	suite.OnlyRunForTags(tagsuite.Python, tagsuite.Activate, tagsuite.Shell)
    38  	if _, err := exec.LookPath("zsh"); err != nil {
    39  		suite.T().Skip("This test requires a zsh shell in your PATH")
    40  	}
    41  	suite.activatePython("3", "SHELL=zsh")
    42  }
    43  
    44  func (suite *ActivateIntegrationTestSuite) TestActivatePython2() {
    45  	suite.OnlyRunForTags(tagsuite.Python, tagsuite.Activate)
    46  	suite.activatePython("2")
    47  }
    48  
    49  func (suite *ActivateIntegrationTestSuite) TestActivateWithoutRuntime() {
    50  	suite.OnlyRunForTags(tagsuite.Critical, tagsuite.Activate, tagsuite.ExitCode)
    51  	ts := e2e.New(suite.T(), false)
    52  	defer ts.Close()
    53  	close := suite.addForegroundSvc(ts)
    54  	defer close()
    55  
    56  	cp := ts.Spawn("activate", "ActiveState-CLI/Python2")
    57  	cp.Expect("Skipping runtime setup")
    58  	cp.Expect("Activated")
    59  	cp.ExpectInput()
    60  
    61  	cp.SendLine("exit 123")
    62  	cp.ExpectExitCode(123)
    63  }
    64  
    65  // addForegroundSvc launches the state-svc in a way where we can track its output for debugging purposes
    66  // without this we are mostly blind to the svc exiting prematurely
    67  func (suite *ActivateIntegrationTestSuite) addForegroundSvc(ts *e2e.Session) func() {
    68  	cmd, stdout, stderr, err := osutils.ExecuteInBackground(ts.SvcExe, []string{"foreground"}, func(cmd *exec.Cmd) error {
    69  		cmd.Env = append(ts.Env, "VERBOSE=true", "") // For whatever reason the last entry is ignored..
    70  		return nil
    71  	})
    72  	suite.Require().NoError(err)
    73  
    74  	// Wait for the svc to be ready
    75  	err = rtutils.Timeout(func() error {
    76  		code := -1
    77  		for code != 0 {
    78  			code, _, _ = osutils.Execute(ts.SvcExe, []string{"status"}, func(cmd *exec.Cmd) error {
    79  				cmd.Env = ts.Env
    80  				return nil
    81  			})
    82  		}
    83  		return nil
    84  	}, 10*time.Second)
    85  	suite.Require().NoError(err)
    86  
    87  	// This function seems to trigger lots of flisten errors that do not appear to be actual errors
    88  	// (the integration test expectations all pass). Just ignore log errors for sessions that call
    89  	// this function.
    90  	ts.IgnoreLogErrors()
    91  
    92  	// Stop function
    93  	return func() {
    94  		go func() {
    95  			defer func() {
    96  				suite.Require().Nil(recover())
    97  			}()
    98  			stdout, stderr, err := osutils.ExecSimple(ts.SvcExe, []string{"stop"}, ts.Env)
    99  			suite.Require().NoError(err, "svc stop failed: %s\n%s", stdout, stderr)
   100  		}()
   101  
   102  		verifyExit := true
   103  
   104  		err2 := rtutils.Timeout(func() error { return cmd.Wait() }, 10*time.Second)
   105  		if err2 != nil {
   106  			if !errors.Is(err2, rtutils.ErrTimeout) {
   107  				suite.Require().NoError(err2)
   108  			}
   109  			suite.T().Logf("svc did not stop in time, Stdout:\n%s\n\nStderr:\n%s", stdout.String(), stderr.String())
   110  			err = cmd.Process.Kill()
   111  			suite.Require().NoError(err)
   112  		}
   113  
   114  		errMsg := fmt.Sprintf("svc foreground did not complete as expected. Stdout:\n%s\n\nStderr:\n%s", stdout.String(), stderr.String())
   115  		if verifyExit {
   116  			suite.Require().NoError(err2, errMsg)
   117  			if cmd.ProcessState.ExitCode() != 0 {
   118  				suite.FailNow(errMsg)
   119  			}
   120  		}
   121  
   122  		// Goroutines don't necessarily cause the process to exit non-zero, so check for common errors/panics
   123  		rx := regexp.MustCompile(`(?:runtime error|invalid memory address|nil pointer|goroutine)`)
   124  		if rx.Match(stderr.Bytes()) {
   125  			suite.FailNow(errMsg)
   126  		}
   127  	}
   128  }
   129  
   130  func (suite *ActivateIntegrationTestSuite) TestActivateUsingCommitID() {
   131  	suite.OnlyRunForTags(tagsuite.Critical, tagsuite.Activate)
   132  	ts := e2e.New(suite.T(), false)
   133  	defer ts.Close()
   134  	close := suite.addForegroundSvc(ts)
   135  	defer close()
   136  
   137  	cp := ts.SpawnWithOpts(
   138  		e2e.OptArgs("activate", "ActiveState-CLI/Python3#6d9280e7-75eb-401a-9e71-0d99759fbad3", "--path", ts.Dirs.Work),
   139  		e2e.OptAppendEnv(constants.DisableRuntime+"=false"),
   140  	)
   141  	cp.Expect("Activated", e2e.RuntimeSourcingTimeoutOpt)
   142  	cp.ExpectInput()
   143  
   144  	cp.SendLine("exit")
   145  	cp.ExpectExitCode(0)
   146  }
   147  
   148  func (suite *ActivateIntegrationTestSuite) TestActivateNotOnPath() {
   149  	suite.OnlyRunForTags(tagsuite.Critical, tagsuite.Activate)
   150  	ts := e2e.NewNoPathUpdate(suite.T(), false)
   151  	defer ts.Close()
   152  	close := suite.addForegroundSvc(ts)
   153  	defer close()
   154  
   155  	cp := ts.SpawnWithOpts(
   156  		e2e.OptArgs("activate", "activestate-cli/small-python", "--path", ts.Dirs.Work),
   157  	)
   158  	cp.Expect("Skipping runtime setup")
   159  	cp.Expect("Activated")
   160  	cp.ExpectInput(termtest.OptExpectTimeout(10 * time.Second))
   161  
   162  	if runtime.GOOS == "windows" {
   163  		cp.SendLine("doskey /macros | findstr state=")
   164  	} else {
   165  		cp.SendLine("alias state")
   166  	}
   167  	cp.Expect("state=")
   168  
   169  	cp.SendLine("state --version")
   170  	cp.Expect("ActiveState")
   171  
   172  	cp.SendLine("exit")
   173  	cp.ExpectExitCode(0)
   174  }
   175  
   176  // TestActivatePythonByHostOnly Tests whether we are only pulling in the build for the target host
   177  func (suite *ActivateIntegrationTestSuite) TestActivatePythonByHostOnly() {
   178  	suite.OnlyRunForTags(tagsuite.Critical, tagsuite.Activate)
   179  
   180  	ts := e2e.New(suite.T(), false)
   181  	defer ts.Close()
   182  	close := suite.addForegroundSvc(ts)
   183  	defer close()
   184  
   185  	projectName := "Python-LinuxWorks"
   186  	cp := ts.SpawnWithOpts(
   187  		e2e.OptArgs("activate", "cli-integration-tests/"+projectName, "--path="+ts.Dirs.Work),
   188  		e2e.OptAppendEnv(constants.DisableRuntime+"=false"),
   189  	)
   190  
   191  	if runtime.GOOS == "linux" {
   192  		cp.Expect("Creating a Virtual Environment")
   193  		cp.Expect("Activated", e2e.RuntimeSourcingTimeoutOpt)
   194  		cp.ExpectInput(termtest.OptExpectTimeout(40 * time.Second))
   195  		cp.SendLine("exit")
   196  		cp.ExpectExitCode(0)
   197  	} else {
   198  		cp.Expect("Your current platform")
   199  		cp.Expect("does not appear to be configured")
   200  		cp.ExpectNotExitCode(0)
   201  
   202  		if strings.Count(cp.Snapshot(), " x ") != 1 {
   203  			suite.Fail("Expected exactly ONE error message, got: ", cp.Snapshot())
   204  		}
   205  	}
   206  	ts.IgnoreLogErrors()
   207  }
   208  
   209  func (suite *ActivateIntegrationTestSuite) assertCompletedStatusBarReport(snapshot string) {
   210  	// ensure that terminal contains output "Installing x/y" with x, y numbers and x=y
   211  	installingString := regexp.MustCompile(
   212  		"Installing *([0-9]+) */ *([0-9]+)",
   213  	).FindAllStringSubmatch(snapshot, -1)
   214  	suite.Require().Greater(len(installingString), 0, "no match for Installing x / x in\n%s", snapshot)
   215  	le := len(installingString) - 1
   216  	suite.Require().Equalf(
   217  		installingString[le][1], installingString[le][2],
   218  		"expected all artifacts are reported to be installed, got %s in\n%s", installingString[0][0], snapshot,
   219  	)
   220  }
   221  
   222  func (suite *ActivateIntegrationTestSuite) activatePython(version string, extraEnv ...string) {
   223  	ts := e2e.New(suite.T(), false)
   224  	defer ts.Close()
   225  	close := suite.addForegroundSvc(ts)
   226  	defer close()
   227  
   228  	namespace := "ActiveState-CLI/Python" + version
   229  
   230  	cp := ts.SpawnWithOpts(
   231  		e2e.OptArgs("activate", namespace),
   232  		e2e.OptAppendEnv(constants.DisableRuntime+"=false"),
   233  		e2e.OptAppendEnv(extraEnv...),
   234  	)
   235  
   236  	cp.Expect("Activated", e2e.RuntimeSourcingTimeoutOpt)
   237  	// ensure that shell is functional
   238  	cp.ExpectInput()
   239  
   240  	pythonExe := "python" + version
   241  
   242  	cp.SendLine(pythonExe + " -c \"import sys; print(sys.copyright)\"")
   243  	cp.Expect("ActiveState Software Inc.")
   244  
   245  	if runtime.GOOS == "windows" {
   246  		cp.SendLine("where " + pythonExe)
   247  		cp.Expect(`\exec\` + pythonExe)
   248  	} else {
   249  		cp.SendLine("which " + pythonExe)
   250  		cp.Expect("/exec/" + pythonExe)
   251  	}
   252  
   253  	cp.SendLine(pythonExe + " -c \"import pytest; print(pytest.__doc__)\"")
   254  	cp.Expect("unit and functional testing")
   255  
   256  	cp.SendLine("state activate --default ActiveState-CLI/cli")
   257  	cp.Expect("Cannot make ActiveState-CLI/cli always available for use while in an activated state")
   258  
   259  	cp.SendLine("state activate --default")
   260  	cp.Expect("Creating a Virtual Environment")
   261  	cp.ExpectInput(termtest.OptExpectTimeout(40 * time.Second))
   262  	pythonShim := pythonExe + osutils.ExeExtension
   263  
   264  	// test that existing environment variables are inherited by the activated shell
   265  	if runtime.GOOS == "windows" {
   266  		cp.SendLine(fmt.Sprintf("echo %%%s%%", constants.DisableRuntime))
   267  	} else {
   268  		cp.SendLine("echo $" + constants.DisableRuntime)
   269  	}
   270  	cp.Expect("false")
   271  
   272  	// test that other executables that use python work as well
   273  	pipExe := "pip" + version
   274  	cp.SendLine(fmt.Sprintf("%s --version", pipExe))
   275  
   276  	// Exit activated state
   277  	cp.SendLine("exit")
   278  	cp.ExpectExitCode(0)
   279  	pendingOutput := cp.PendingOutput() // Without waiting for exit this isn't guaranteed to have our output yet
   280  
   281  	// Assert pip output
   282  	pipVersionRe := regexp.MustCompile(`pip \d+(?:\.\d+)+ from ([^ ]+) \(python`)
   283  	pipVersionMatch := pipVersionRe.FindStringSubmatch(pendingOutput)
   284  	suite.Require().Len(pipVersionMatch, 2, "expected pip version to match, pending output: %s", pendingOutput)
   285  	suite.Contains(pipVersionMatch[1], "cache", "pip loaded from activestate cache dir")
   286  
   287  	executor := filepath.Join(ts.Dirs.DefaultBin, pythonShim)
   288  	// check that default activation works
   289  	cp = ts.SpawnCmdWithOpts(
   290  		executor,
   291  		e2e.OptArgs("-c", "import sys; print(sys.copyright);"),
   292  		e2e.OptAppendEnv(constants.DisableRuntime+"=false"),
   293  	)
   294  	cp.Expect("ActiveState Software Inc.", e2e.RuntimeSourcingTimeoutOpt)
   295  	cp.ExpectExitCode(0)
   296  }
   297  
   298  func (suite *ActivateIntegrationTestSuite) TestActivate_PythonPath() {
   299  	suite.OnlyRunForTags(tagsuite.Activate)
   300  
   301  	ts := e2e.New(suite.T(), false)
   302  	defer ts.Close()
   303  	close := suite.addForegroundSvc(ts)
   304  	defer close()
   305  
   306  	namespace := "ActiveState-CLI/Python3"
   307  
   308  	cp := ts.SpawnWithOpts(
   309  		e2e.OptArgs("activate", namespace),
   310  		e2e.OptAppendEnv(constants.DisableRuntime+"=false"),
   311  	)
   312  
   313  	cp.Expect("Activated", e2e.RuntimeSourcingTimeoutOpt)
   314  	// ensure that shell is functional
   315  	cp.ExpectInput()
   316  
   317  	// Verify that PYTHONPATH is set correctly to the installed site-packages, not a temp runtime
   318  	// setup directory.
   319  	if runtime.GOOS == "windows" {
   320  		cp.SendLine("echo %PYTHONPATH%")
   321  	} else {
   322  		cp.SendLine("echo $PYTHONPATH")
   323  	}
   324  	suite.Assert().NotContains(cp.Output(), constants.LocalRuntimeTempDirectory)
   325  	// Verify the temp runtime setup directory has been removed.
   326  	runtimeFound := false
   327  	entries, err := fileutils.ListDir(ts.Dirs.Cache, true)
   328  	suite.Require().NoError(err)
   329  	for _, entry := range entries {
   330  		if entry.IsDir() && fileutils.DirExists(filepath.Join(entry.Path(), constants.LocalRuntimeEnvironmentDirectory)) {
   331  			runtimeFound = true
   332  			suite.Assert().NoDirExists(filepath.Join(entry.Path(), constants.LocalRuntimeTempDirectory))
   333  		}
   334  	}
   335  	suite.Assert().True(runtimeFound, "runtime directory was not found in ts.Dirs.Cache")
   336  
   337  	// test that PYTHONPATH is preserved in environment (https://www.pivotaltracker.com/story/show/178458102)
   338  	if runtime.GOOS == "windows" {
   339  		cp.SendLine("set PYTHONPATH=/custom_pythonpath")
   340  		cp.SendLine(`python3 -c "import os; print(os.environ['PYTHONPATH']);"`)
   341  	} else {
   342  		cp.SendLine(`PYTHONPATH=/custom_pythonpath python3 -c 'import os; print(os.environ["PYTHONPATH"]);'`)
   343  	}
   344  	cp.Expect("/custom_pythonpath")
   345  
   346  	// de-activate shell
   347  	cp.SendLine("exit")
   348  	cp.ExpectExitCode(0)
   349  }
   350  
   351  func (suite *ActivateIntegrationTestSuite) TestActivate_SpaceInCacheDir() {
   352  	suite.OnlyRunForTags(tagsuite.Activate)
   353  
   354  	ts := e2e.New(suite.T(), false)
   355  	defer ts.Close()
   356  	close := suite.addForegroundSvc(ts)
   357  	defer close()
   358  
   359  	cacheDir := filepath.Join(ts.Dirs.Cache, "dir with spaces")
   360  	err := fileutils.MkdirUnlessExists(cacheDir)
   361  	suite.Require().NoError(err)
   362  
   363  	cp := ts.SpawnWithOpts(
   364  		e2e.OptAppendEnv(fmt.Sprintf("%s=%s", constants.CacheEnvVarName, cacheDir)),
   365  		e2e.OptAppendEnv(fmt.Sprintf(`%s=""`, constants.DisableRuntime)),
   366  		e2e.OptArgs("activate", "ActiveState-CLI/Python3"),
   367  	)
   368  
   369  	cp.Expect("Activated", e2e.RuntimeSourcingTimeoutOpt)
   370  	cp.SendLine("python3 --version")
   371  	cp.Expect("Python 3.")
   372  
   373  	cp.SendLine("exit")
   374  	cp.ExpectExitCode(0)
   375  }
   376  
   377  func (suite *ActivateIntegrationTestSuite) TestActivatePerlCamel() {
   378  	suite.OnlyRunForTags(tagsuite.Activate, tagsuite.Perl, tagsuite.Critical)
   379  	if runtime.GOOS == "darwin" {
   380  		suite.T().Skip("Perl not supported on macOS")
   381  	}
   382  
   383  	ts := e2e.New(suite.T(), false)
   384  	defer ts.Close()
   385  	close := suite.addForegroundSvc(ts)
   386  	defer close()
   387  
   388  	cp := ts.SpawnWithOpts(
   389  		e2e.OptArgs("activate", "ActiveState-CLI/Perl"),
   390  		e2e.OptAppendEnv(constants.DisableRuntime+"=false"),
   391  	)
   392  
   393  	cp.Expect("Downloading", termtest.OptExpectTimeout(40*time.Second))
   394  	cp.Expect("Installing", termtest.OptExpectTimeout(140*time.Second))
   395  	cp.Expect("Activated", e2e.RuntimeSourcingTimeoutOpt)
   396  
   397  	suite.assertCompletedStatusBarReport(cp.Output())
   398  
   399  	// ensure that shell is functional
   400  	cp.ExpectInput()
   401  
   402  	cp.SendLine("perldoc -l DBI::DBD")
   403  	// Expect the source code to be installed in the cache directory
   404  	// Note: At least for Windows we cannot expect cp.Dirs.Cache, because it is unreliable how the path name formats are unreliable (sometimes DOS 8.3 format, sometimes not)
   405  	cp.Expect("cache")
   406  	cp.Expect("DBD.pm")
   407  
   408  	// Currently CI is searching for PPM in the @INC first before attempting
   409  	// to execute a script. https://activestatef.atlassian.net/browse/DX-620
   410  	if runtime.GOOS != "windows" {
   411  		// Expect PPM shim to be installed
   412  		cp.SendLine("ppm list")
   413  		cp.Expect("Shimming command")
   414  	}
   415  
   416  	cp.SendLine("exit")
   417  	cp.ExpectExitCode(0)
   418  }
   419  
   420  func (suite *ActivateIntegrationTestSuite) TestActivate_Subdir() {
   421  	suite.OnlyRunForTags(tagsuite.Activate, tagsuite.Critical)
   422  	ts := e2e.New(suite.T(), false)
   423  	defer ts.Close()
   424  	close := suite.addForegroundSvc(ts)
   425  	defer close()
   426  	err := fileutils.Mkdir(ts.Dirs.Work, "foo", "bar", "baz")
   427  	suite.Require().NoError(err)
   428  
   429  	// Create the project file at the root of the temp dir
   430  	content := strings.TrimSpace(fmt.Sprintf(`
   431  project: "https://platform.activestate.com/ActiveState-CLI/Python3"
   432  branch: %s
   433  version: %s
   434  `, constants.ChannelName, constants.Version))
   435  
   436  	ts.PrepareActiveStateYAML(content)
   437  	ts.PrepareCommitIdFile("59404293-e5a9-4fd0-8843-77cd4761b5b5")
   438  
   439  	// Pull to ensure we have an up to date config file
   440  	cp := ts.Spawn("pull")
   441  	cp.Expect("activestate.yaml has been updated to")
   442  	cp.ExpectExitCode(0)
   443  
   444  	// Activate in the subdirectory
   445  	c2 := ts.SpawnWithOpts(
   446  		e2e.OptArgs("activate"),
   447  		e2e.OptWD(filepath.Join(ts.Dirs.Work, "foo", "bar", "baz")),
   448  	)
   449  	c2.Expect("Activated")
   450  
   451  	c2.ExpectInput(termtest.OptExpectTimeout(40 * time.Second))
   452  	c2.SendLine("exit")
   453  	c2.ExpectExitCode(0)
   454  }
   455  
   456  func (suite *ActivateIntegrationTestSuite) TestActivate_NamespaceWins() {
   457  	suite.OnlyRunForTags(tagsuite.Activate)
   458  	ts := e2e.New(suite.T(), false)
   459  	identifyPath := "identifyable-path"
   460  	targetPath := filepath.Join(ts.Dirs.Work, "foo", "bar", identifyPath)
   461  	defer ts.Close()
   462  	close := suite.addForegroundSvc(ts)
   463  	defer close()
   464  	err := fileutils.Mkdir(targetPath)
   465  	suite.Require().NoError(err)
   466  
   467  	// Create the project file at the root of the temp dir
   468  	ts.PrepareProject("ActiveState-CLI/Python3", "59404293-e5a9-4fd0-8843-77cd4761b5b5")
   469  
   470  	// Pull to ensure we have an up to date config file
   471  	cp := ts.Spawn("pull")
   472  	cp.Expect("activestate.yaml has been updated to")
   473  	cp.ExpectExitCode(0)
   474  
   475  	// Activate in the subdirectory
   476  	c2 := ts.SpawnWithOpts(
   477  		e2e.OptArgs("activate", "ActiveState-CLI/Python2"), // activate a different namespace
   478  		e2e.OptWD(targetPath),
   479  		e2e.OptAppendEnv(constants.DisableLanguageTemplates+"=true"),
   480  	)
   481  	c2.Expect("ActiveState-CLI/Python2")
   482  	c2.Expect("Activated")
   483  
   484  	c2.ExpectInput(termtest.OptExpectTimeout(40 * time.Second))
   485  	if runtime.GOOS == "windows" {
   486  		c2.SendLine("@echo %cd%")
   487  	} else {
   488  		c2.SendLine("pwd")
   489  	}
   490  	c2.Expect(identifyPath)
   491  	c2.SendLine("exit")
   492  	c2.ExpectExitCode(0)
   493  }
   494  
   495  func (suite *ActivateIntegrationTestSuite) TestActivate_InterruptedInstallation() {
   496  	suite.OnlyRunForTags(tagsuite.Activate)
   497  	if runtime.GOOS == "windows" && e2e.RunningOnCI() {
   498  		suite.T().Skip("interrupting installation does not work on Windows on CI")
   499  	}
   500  	ts := e2e.New(suite.T(), true)
   501  	defer ts.Close()
   502  	close := suite.addForegroundSvc(ts)
   503  	defer close()
   504  
   505  	cp := ts.SpawnShellWithOpts("bash", e2e.OptAppendEnv(constants.DisableRuntime+"=false"))
   506  	cp.SendLine("state deploy install ActiveState-CLI/small-python")
   507  	cp.Expect("Installing Runtime") // Ensure we don't send Ctrl+C too soon
   508  	cp.SendCtrlC()
   509  	cp.Expect("User interrupted")
   510  	cp.SendLine("exit")
   511  	cp.ExpectExit()
   512  }
   513  
   514  func (suite *ActivateIntegrationTestSuite) TestActivate_FromCache() {
   515  	suite.OnlyRunForTags(tagsuite.Activate, tagsuite.Critical)
   516  	ts := e2e.New(suite.T(), true)
   517  	err := ts.ClearCache()
   518  	suite.Require().NoError(err)
   519  	defer ts.Close()
   520  	close := suite.addForegroundSvc(ts)
   521  	defer close()
   522  
   523  	cp := ts.SpawnWithOpts(
   524  		e2e.OptArgs("activate", "ActiveState-CLI/small-python", "--path", ts.Dirs.Work),
   525  		e2e.OptAppendEnv(constants.DisableRuntime+"=false"),
   526  	)
   527  	cp.Expect("Downloading")
   528  	cp.Expect("Installing")
   529  	cp.Expect("Activated", e2e.RuntimeSourcingTimeoutOpt)
   530  
   531  	suite.assertCompletedStatusBarReport(cp.Output())
   532  	cp.SendLine("exit")
   533  	cp.ExpectExitCode(0)
   534  
   535  	// next activation is cached
   536  	cp = ts.SpawnWithOpts(
   537  		e2e.OptArgs("activate", "ActiveState-CLI/small-python", "--path", ts.Dirs.Work),
   538  		e2e.OptAppendEnv(constants.DisableRuntime+"=false"),
   539  	)
   540  
   541  	cp.ExpectInput(e2e.RuntimeSourcingTimeoutOpt)
   542  	cp.SendLine("exit")
   543  	cp.ExpectExitCode(0)
   544  	suite.NotContains(cp.Output(), "Downloading")
   545  }
   546  
   547  func TestActivateIntegrationTestSuite(t *testing.T) {
   548  	suite.Run(t, new(ActivateIntegrationTestSuite))
   549  }
   550  
   551  func (suite *ActivateIntegrationTestSuite) TestActivateCommitURL() {
   552  	suite.OnlyRunForTags(tagsuite.Activate)
   553  	ts := e2e.New(suite.T(), false)
   554  	defer ts.Close()
   555  	close := suite.addForegroundSvc(ts)
   556  	defer close()
   557  
   558  	// https://platform.activestate.com/ActiveState-CLI/Python3/customize?commitID=fbc613d6-b0b1-4f84-b26e-4aa5869c4e54
   559  	commitID := "fbc613d6-b0b1-4f84-b26e-4aa5869c4e54"
   560  	contents := fmt.Sprintf("project: https://platform.activestate.com/commit/%s\n", commitID)
   561  	ts.PrepareActiveStateYAML(contents)
   562  
   563  	cp := ts.Spawn("activate")
   564  	cp.Expect("Cannot operate on a headless project", e2e.RuntimeSourcingTimeoutOpt)
   565  	cp.ExpectExitCode(1)
   566  	ts.IgnoreLogErrors()
   567  }
   568  
   569  func (suite *ActivateIntegrationTestSuite) TestActivate_AlreadyActive() {
   570  	suite.OnlyRunForTags(tagsuite.Activate)
   571  
   572  	ts := e2e.New(suite.T(), false)
   573  	defer ts.Close()
   574  	close := suite.addForegroundSvc(ts)
   575  	defer close()
   576  
   577  	namespace := "ActiveState-CLI/Python3"
   578  
   579  	cp := ts.SpawnWithOpts(e2e.OptArgs("activate", namespace))
   580  	cp.Expect("Skipping runtime setup")
   581  	cp.Expect("Activated")
   582  	// ensure that shell is functional
   583  	cp.ExpectInput()
   584  
   585  	cp.SendLine("state activate")
   586  	cp.Expect("Your project is already active")
   587  	cp.ExpectInput()
   588  }
   589  
   590  func (suite *ActivateIntegrationTestSuite) TestActivate_AlreadyActive_SameNamespace() {
   591  	suite.OnlyRunForTags(tagsuite.Activate)
   592  
   593  	ts := e2e.New(suite.T(), false)
   594  	defer ts.Close()
   595  	close := suite.addForegroundSvc(ts)
   596  	defer close()
   597  
   598  	namespace := "ActiveState-CLI/Python3"
   599  
   600  	cp := ts.SpawnWithOpts(e2e.OptArgs("activate", namespace))
   601  	cp.Expect("Skipping runtime setup")
   602  	cp.Expect("Activated")
   603  	// ensure that shell is functional
   604  	cp.ExpectInput()
   605  
   606  	cp.SendLine(fmt.Sprintf("state activate %s", namespace))
   607  	cp.Expect("Your project is already active")
   608  	cp.ExpectInput()
   609  }
   610  
   611  func (suite *ActivateIntegrationTestSuite) TestActivate_AlreadyActive_DifferentNamespace() {
   612  	suite.OnlyRunForTags(tagsuite.Activate)
   613  
   614  	ts := e2e.New(suite.T(), false)
   615  	defer ts.Close()
   616  	close := suite.addForegroundSvc(ts)
   617  	defer close()
   618  
   619  	namespace := "ActiveState-CLI/Python3"
   620  
   621  	cp := ts.SpawnWithOpts(e2e.OptArgs("activate", namespace))
   622  	cp.Expect("Skipping runtime setup")
   623  	cp.Expect("Activated")
   624  	// ensure that shell is functional
   625  	cp.ExpectInput()
   626  
   627  	cp.SendLine(fmt.Sprintf("state activate %s", "ActiveState-CLI/Perl-5.32"))
   628  	cp.Expect("You cannot activate a new project when you are already in an activated state")
   629  	cp.ExpectInput()
   630  }
   631  
   632  func (suite *ActivateIntegrationTestSuite) TestActivateBranch() {
   633  	suite.OnlyRunForTags(tagsuite.Activate)
   634  
   635  	ts := e2e.New(suite.T(), false)
   636  	defer ts.Close()
   637  	close := suite.addForegroundSvc(ts)
   638  	defer close()
   639  
   640  	namespace := "ActiveState-CLI/Branches"
   641  
   642  	cp := ts.SpawnWithOpts(
   643  		e2e.OptArgs("activate", namespace, "--branch", "firstbranch"),
   644  	)
   645  	cp.Expect("Skipping runtime setup")
   646  	cp.Expect("Activated")
   647  	cp.SendLine("exit")
   648  	cp.ExpectExitCode(0)
   649  }
   650  
   651  func (suite *ActivateIntegrationTestSuite) TestActivateBranchNonExistant() {
   652  	suite.OnlyRunForTags(tagsuite.Activate)
   653  
   654  	ts := e2e.New(suite.T(), false)
   655  	defer ts.Close()
   656  	close := suite.addForegroundSvc(ts)
   657  	defer close()
   658  
   659  	namespace := "ActiveState-CLI/Branches"
   660  
   661  	cp := ts.SpawnWithOpts(e2e.OptArgs("activate", namespace, "--branch", "does-not-exist"))
   662  
   663  	cp.Expect("has no branch")
   664  }
   665  
   666  func (suite *ActivateIntegrationTestSuite) TestActivateArtifactsCached() {
   667  	suite.OnlyRunForTags(tagsuite.Activate)
   668  
   669  	ts := e2e.New(suite.T(), false)
   670  	defer ts.Close()
   671  	close := suite.addForegroundSvc(ts)
   672  	defer close()
   673  
   674  	namespace := "ActiveState-CLI/Python3"
   675  
   676  	cp := ts.SpawnWithOpts(
   677  		e2e.OptArgs("activate", namespace),
   678  		e2e.OptAppendEnv(constants.DisableRuntime+"=false"),
   679  	)
   680  
   681  	cp.Expect("Activated", e2e.RuntimeSourcingTimeoutOpt)
   682  	cp.SendLine("exit")
   683  	cp.ExpectExitCode(0)
   684  
   685  	artifactCacheDir := filepath.Join(ts.Dirs.Cache, constants.ArtifactMetaDir)
   686  	suite.True(fileutils.DirExists(artifactCacheDir), "artifact cache directory does not exist")
   687  	artifactInfoJson := filepath.Join(artifactCacheDir, constants.ArtifactCacheFileName)
   688  	suite.True(fileutils.FileExists(artifactInfoJson), "artifact cache info json file does not exist")
   689  
   690  	files, err := fileutils.ListDir(artifactCacheDir, false)
   691  	suite.NoError(err)
   692  	suite.True(len(files) > 1, "artifact cache is empty") // ignore json file
   693  
   694  	// Clear all cached data except artifact cache.
   695  	// This removes the runtime so that it needs to be created again.
   696  	files, err = fileutils.ListDir(ts.Dirs.Cache, true)
   697  	suite.NoError(err)
   698  	for _, entry := range files {
   699  		if entry.IsDir() && entry.RelativePath() != constants.ArtifactMetaDir {
   700  			os.RemoveAll(entry.Path())
   701  		}
   702  	}
   703  
   704  	cp = ts.SpawnWithOpts(
   705  		e2e.OptArgs("activate", namespace),
   706  		e2e.OptAppendEnv(
   707  			constants.DisableRuntime+"=false",
   708  			"VERBOSE=true", // Necessary to assert "Fetched cached artifact"
   709  		),
   710  	)
   711  
   712  	cp.Expect("Fetched cached artifact")
   713  	cp.Expect("Activated", e2e.RuntimeSourcingTimeoutOpt)
   714  	cp.SendLine("exit")
   715  	cp.ExpectExitCode(0)
   716  }