Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reimplement integration test sandboxing #3006

Merged
merged 6 commits into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions internal/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,9 @@ const SvcAuthPollingRateEnvVarName = "ACTIVESTATE_SVC_AUTH_POLLING_RATE"
// log rotation timer interval (1 minute).
const SvcLogRotateIntervalEnvVarName = "ACTIVESTATE_CLI_LOG_ROTATE_INTERVAL_MS"

// DisableActivateEventsEnvVarName is the environment variable used to disable events when activating or checking out a project
const DisableActivateEventsEnvVarName = "ACTIVESTATE_CLI_DISABLE_ACTIVATE_EVENTS"

// APIUpdateInfoURL is the URL for our update info server
const APIUpdateInfoURL = "https://platform.activestate.com/sv/state-update/api/v1"

Expand Down Expand Up @@ -528,3 +531,6 @@ const PlatformApiPrintRequestsEnvVarName = "ACTIVESTATE_CLI_PLATFORM_API_PRINT_R

// ActiveStateCIEnvVarName is the environment variable set when running in an ActiveState CI environment.
const ActiveStateCIEnvVarName = "ACTIVESTATE_CI"

// OverrideSandbox is the environment variable to set when overriding the sandbox for integration tests.
const OverrideSandbox = "ACTIVESTATE_TEST_OVERRIDE_SANDBOX"
4 changes: 1 addition & 3 deletions internal/osutils/exeutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,8 @@ func ExecSimpleFromDir(dir, bin string, args []string, env []string) (string, st

// Execute will run the given command and with optional settings for the exec.Cmd struct
func Execute(command string, arg []string, optSetter func(cmd *exec.Cmd) error) (int, *exec.Cmd, error) {
logging.Debug("Executing command: %s, %v", command, arg)

cmd := exec.Command(command, arg...)

logging.Debug("Executing command: %s, with args: %s", cmd, arg)
if optSetter != nil {
if err := optSetter(cmd); err != nil {
return -1, nil, err
Expand Down
2 changes: 1 addition & 1 deletion internal/osutils/osutils_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func BashifyPathEnv(pathList string) (string, error) {
cmd.Env = []string{"PATH=" + pathList}
bashified, err := cmd.Output()
if err != nil {
return "", errs.Wrap(err, "Unable to bashify PATH: %s", pathList)
return "", errs.Wrap(err, "Unable to bashify PATH: %s, output: %s", pathList, string(bashified))
}
return string(bashified), nil
}
Expand Down
1 change: 1 addition & 0 deletions internal/osutils/shortcut/shortcut_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ func (s *Shortcut) Enable() error {
if err != nil {
oleErr := &ole.OleError{}
if errors.As(err, &oleErr) {
logging.Debug("OLE Error details: %s\n%s\n%s\n%s\n%s", oleErr.Code(), oleErr.Description(), oleErr.Error(), oleErr.String(), oleErr.SubError())
return errs.Wrap(err, "oleutil CreateShortcut returned error: %s", oleErr.String())
}
return errs.Wrap(err, "Could not call CreateShortcut on shell object")
Expand Down
6 changes: 5 additions & 1 deletion internal/testhelpers/e2e/dirs.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ type Dirs struct {
SockRoot string
// HomeDir is used as the test user's home directory
HomeDir string
// TempDir is the directory where temporary files are stored
TempDir string
}

// NewDirs creates all temporary directories
Expand All @@ -42,8 +44,9 @@ func NewDirs(base string) (*Dirs, error) {
defaultBin := filepath.Join(base, "cache", "bin")
sockRoot := filepath.Join(base, "sock")
homeDir := filepath.Join(base, "home")
tempDir := filepath.Join(base, "temp")

subdirs := []string{config, cache, bin, work, defaultBin}
subdirs := []string{config, cache, bin, work, defaultBin, sockRoot, homeDir, tempDir}
for _, subdir := range subdirs {
if err := os.MkdirAll(subdir, 0700); err != nil {
return nil, err
Expand All @@ -59,6 +62,7 @@ func NewDirs(base string) (*Dirs, error) {
DefaultBin: defaultBin,
SockRoot: sockRoot,
HomeDir: homeDir,
TempDir: tempDir,
}

return &dirs, nil
Expand Down
117 changes: 117 additions & 0 deletions internal/testhelpers/e2e/env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package e2e

import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"

"github.com/ActiveState/cli/internal/constants"
"github.com/ActiveState/cli/internal/errs"
"github.com/ActiveState/cli/internal/fileutils"
"github.com/stretchr/testify/require"
)

func sandboxedTestEnvironment(t *testing.T, dirs *Dirs, updatePath bool, extraEnv ...string) []string {
var env []string
basePath := platformPath()
if os.Getenv(constants.OverrideSandbox) != "" {
basePath = os.Getenv("PATH")
env = append(env, os.Environ()...)
}
if value := os.Getenv(constants.ActiveStateCIEnvVarName); value != "" {
env = append(env, fmt.Sprintf("%s=%s", constants.ActiveStateCIEnvVarName, value))
}

// add go binary to PATH
goBinary := goBinaryPath(t)
basePath = fmt.Sprintf("%s%s%s", basePath, string(os.PathListSeparator), filepath.Dir(goBinary))

env = append(env, []string{
constants.ConfigEnvVarName + "=" + dirs.Config,
constants.CacheEnvVarName + "=" + dirs.Cache,
constants.DisableRuntime + "=true",
constants.ProjectEnvVarName + "=",
constants.E2ETestEnvVarName + "=true",
constants.DisableUpdates + "=true",
constants.DisableProjectMigrationPrompt + "=true",
constants.OptinUnstableEnvVarName + "=true",
constants.ServiceSockDir + "=" + dirs.SockRoot,
constants.HomeEnvVarName + "=" + dirs.HomeDir,
systemHomeEnvVarName + "=" + dirs.HomeDir,
"NO_COLOR=true",
"CI=true",
}...)

if updatePath {
// add bin path
oldPath := basePath
newPath := fmt.Sprintf(
"PATH=%s%s%s",
dirs.Bin, string(os.PathListSeparator), oldPath,
)
env = append(env, newPath)
} else {
env = append(env, "PATH="+basePath)
}

// append platform specific environment variables
env = append(env, platformSpecificEnv(dirs)...)

// Prepare sandboxed home directory
err := prepareHomeDir(dirs.HomeDir)
require.NoError(t, err)

// add session environment variables
env = append(env, extraEnv...)

return env
}

func prepareHomeDir(dir string) error {
if runtime.GOOS == "windows" {
return nil
}

if !fileutils.DirExists(dir) {
err := fileutils.Mkdir(dir)
if err != nil {
return errs.Wrap(err, "Could not create home dir")
}
}

var filename string
switch runtime.GOOS {
case "linux":
filename = ".bashrc"
case "darwin":
filename = ".zshrc"
}

rcFile := filepath.Join(dir, filename)
err := fileutils.Touch(rcFile)
if err != nil {
return errs.Wrap(err, "Could not create rc file")
}

return nil
}

func goBinaryPath(t *testing.T) string {
locator := "which"
if runtime.GOOS == "windows" {
locator = "where"
}
cmd := exec.Command(locator, "go")
output, err := cmd.Output()
if err != nil {
t.Log("Could not find go binary")
return ""
}
goBinary := string(output)
goBinary = strings.TrimSpace(string(goBinary))
return goBinary
}
17 changes: 17 additions & 0 deletions internal/testhelpers/e2e/env_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//go:build !windows
// +build !windows

package e2e

const (
basePath = "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local:/usr/local/sbin:/usr/local/opt"
systemHomeEnvVarName = "HOME"
)

func platformSpecificEnv(dirs *Dirs) []string {
return nil
}

func platformPath() string {
return basePath
}
49 changes: 49 additions & 0 deletions internal/testhelpers/e2e/env_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//go:build windows
// +build windows

package e2e

import (
"fmt"
"os"

"github.com/ActiveState/cli/internal/condition"
"github.com/ActiveState/cli/internal/constants"
)

const (
basePath = `C:\Windows\System32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files\PowerShell\7\;`
systemHomeEnvVarName = "USERPROFILE"
)

func platformSpecificEnv(dirs *Dirs) []string {
return []string{
"SystemDrive=C:",
"SystemRoot=C:\\Windows",
"PROGRAMFILES=C:\\Program Files",
"ProgramFiles(x86)=C:\\Program Files (x86)",
"PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC",
"HOMEDRIVE=C:",
"ALLUSERSPROFILE=C:\\ProgramData",
"ProgramData=C:\\ProgramData",
"COMSPEC=C:\\Windows\\System32\\cmd.exe",
"PROGRAMFILES=C:\\Program Files",
"CommonProgramW6432=C:\\Program Files\\Common Files",
"WINDIR=C:\\Windows",
"PUBLIC=C:\\Users\\Public",
"PSModuleAnalysisCachePath=C:\\PSModuleAnalysisCachePath\\ModuleAnalysisCache",
fmt.Sprintf("HOMEPATH=%s", dirs.HomeDir),
// Other environment variables are commonly set by CI systems, but this one is not.
// This is requried for some tests in order to get the correct powershell output.
fmt.Sprintf("PSModulePath=%s", os.Getenv("PSModulePath")),
fmt.Sprintf("LOCALAPPDATA=%s", dirs.TempDir),
fmt.Sprintf("%s=true", constants.DisableActivateEventsEnvVarName),
}
}

func platformPath() string {
if condition.OnCI() {
return `C:\msys64\usr\bin` + string(os.PathListSeparator) + basePath
}
return basePath
}
65 changes: 4 additions & 61 deletions internal/testhelpers/e2e/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,57 +172,7 @@ func New(t *testing.T, retainDirs bool, extraEnv ...string) *Session {
func new(t *testing.T, retainDirs, updatePath bool, extraEnv ...string) *Session {
dirs, err := NewDirs("")
require.NoError(t, err)
var env []string
env = append(env, os.Environ()...)
env = append(env, []string{
constants.ConfigEnvVarName + "=" + dirs.Config,
constants.CacheEnvVarName + "=" + dirs.Cache,
constants.DisableRuntime + "=true",
constants.ProjectEnvVarName + "=",
constants.E2ETestEnvVarName + "=true",
constants.DisableUpdates + "=true",
constants.DisableProjectMigrationPrompt + "=true",
constants.OptinUnstableEnvVarName + "=true",
constants.ServiceSockDir + "=" + dirs.SockRoot,
constants.HomeEnvVarName + "=" + dirs.HomeDir,
"NO_COLOR=true",
}...)

if updatePath {
// add bin path
// Remove release state tool installation from PATH in tests
// This is a workaround as our test sessions are not compeltely
// sandboxed. This should be addressed in: https://activestatef.atlassian.net/browse/DX-2285
oldPath, _ := os.LookupEnv("PATH")
installPath, err := installation.InstallPathForChannel("release")
require.NoError(t, err)

binPath := filepath.Join(installPath, "bin")
oldPath = strings.Replace(oldPath, binPath+string(os.PathListSeparator), "", -1)
newPath := fmt.Sprintf(
"PATH=%s%s%s",
dirs.Bin, string(os.PathListSeparator), oldPath,
)
env = append(env, newPath)
t.Setenv("PATH", newPath)

cfg, err := config.New()
require.NoError(t, err)

// In order to ensure that the release state tool does not appear on the PATH
// when a new subshell is started we remove the installation entries from the
// rc file. This is added back later in the session's Close method.
// Again, this is a workaround to be addressed in: https://activestatef.atlassian.net/browse/DX-2285
if runtime.GOOS != "windows" {
s := bash.SubShell{}
err = s.CleanUserEnv(cfg, sscommon.InstallID, false)
require.NoError(t, err)
}
t.Setenv(constants.HomeEnvVarName, dirs.HomeDir)
}

// add session environment variables
env = append(env, extraEnv...)
env := sandboxedTestEnvironment(t, dirs, updatePath, extraEnv...)

session := &Session{Dirs: dirs, Env: env, retainDirs: retainDirs, T: t}

Expand All @@ -232,14 +182,6 @@ func new(t *testing.T, retainDirs, updatePath bool, extraEnv ...string) *Session
session.SvcExe = session.copyExeToBinDir(svcExe)
session.ExecutorExe = session.copyExeToBinDir(execExe)

// Set up environment for test runs. This is separate
// from the environment for the session itself.
// Setting environment variables here allows helper
// functions access to them.
// This is a workaround as our test sessions are not compeltely
// sandboxed. This should be addressed in: https://activestatef.atlassian.net/browse/DX-2285
t.Setenv(constants.HomeEnvVarName, dirs.HomeDir)

err = fileutils.Touch(filepath.Join(dirs.Base, installation.InstallDirMarker))
require.NoError(session.T, err)

Expand Down Expand Up @@ -353,7 +295,6 @@ func (s *Session) SpawnCmdWithOpts(exe string, optSetters ...SpawnOptSetter) *Sp
cmd := exec.Command(shell, args...)

cmd.Env = spawnOpts.Env

if spawnOpts.Dir != "" {
cmd.Dir = spawnOpts.Dir
}
Expand Down Expand Up @@ -771,6 +712,8 @@ func (s *Session) SetupRCFile() {
if runtime.GOOS == "windows" {
return
}
s.T.Setenv("HOME", s.Dirs.HomeDir)
defer s.T.Setenv("HOME", os.Getenv("HOME"))

cfg, err := config.New()
require.NoError(s.T, err)
Expand All @@ -789,7 +732,7 @@ func (s *Session) SetupRCFileCustom(subshell subshell.SubShell) {
if fileutils.TargetExists(filepath.Join(s.Dirs.HomeDir, filepath.Base(rcFile))) {
err = fileutils.CopyFile(rcFile, filepath.Join(s.Dirs.HomeDir, filepath.Base(rcFile)))
} else {
err = fileutils.Touch(rcFile)
err = fileutils.Touch(filepath.Join(s.Dirs.HomeDir, filepath.Base(rcFile)))
}
require.NoError(s.T, err)
}
Expand Down
11 changes: 11 additions & 0 deletions pkg/project/events.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
package project

import (
"os"
"strings"

"github.com/ActiveState/cli/internal/constants"
)

type EventType string

const (
Expand All @@ -14,6 +21,10 @@ func (e EventType) String() string {
}

func ActivateEvents() []EventType {
if strings.EqualFold(os.Getenv(constants.DisableActivateEventsEnvVarName), "true") {
return []EventType{}
}

return []EventType{
Activate,
FirstActivate,
Expand Down
6 changes: 3 additions & 3 deletions test/integration/activate_int_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,10 @@ func (suite *ActivateIntegrationTestSuite) TestActivateUsingCommitID() {

cp := ts.SpawnWithOpts(
e2e.OptArgs("activate", "ActiveState-CLI/Python3#6d9280e7-75eb-401a-9e71-0d99759fbad3", "--path", ts.Dirs.Work),
e2e.OptAppendEnv(constants.DisableRuntime+"=false"),
)
cp.Expect("Skipping runtime setup")
cp.Expect("Activated")
cp.ExpectInput(termtest.OptExpectTimeout(10 * time.Second))
cp.Expect("Activated", e2e.RuntimeSourcingTimeoutOpt)
cp.ExpectInput()

cp.SendLine("exit")
cp.ExpectExitCode(0)
Expand Down
Loading
Loading