github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/test/integration/install_scripts_int_test.go (about) 1 package integration 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "path/filepath" 7 "runtime" 8 "testing" 9 10 "github.com/ActiveState/cli/internal/testhelpers/suite" 11 "github.com/stretchr/testify/require" 12 "github.com/thoas/go-funk" 13 14 anaConst "github.com/ActiveState/cli/internal/analytics/constants" 15 "github.com/ActiveState/cli/internal/condition" 16 "github.com/ActiveState/cli/internal/constants" 17 "github.com/ActiveState/cli/internal/environment" 18 "github.com/ActiveState/cli/internal/fileutils" 19 "github.com/ActiveState/cli/internal/httputil" 20 "github.com/ActiveState/cli/internal/installation" 21 "github.com/ActiveState/cli/internal/osutils" 22 "github.com/ActiveState/cli/internal/testhelpers/e2e" 23 "github.com/ActiveState/cli/internal/testhelpers/tagsuite" 24 ) 25 26 type InstallScriptsIntegrationTestSuite struct { 27 tagsuite.Suite 28 } 29 30 func (suite *InstallScriptsIntegrationTestSuite) TestInstall() { 31 suite.OnlyRunForTags(tagsuite.InstallScripts, tagsuite.Critical) 32 33 tests := []struct { 34 Name string 35 Version string 36 Channel string 37 Activate string 38 ActivateByCommand string 39 }{ 40 // {"install-release-latest", "", "release", "", ""}, 41 {"install-prbranch", "", "", "", ""}, 42 {"install-prbranch-with-version", constants.Version, constants.ChannelName, "", ""}, 43 {"install-prbranch-and-activate", "", constants.ChannelName, "ActiveState-CLI/small-python", ""}, 44 {"install-prbranch-and-activate-by-command", "", constants.ChannelName, "", "ActiveState-CLI/small-python"}, 45 } 46 47 for _, tt := range tests { 48 suite.Run(fmt.Sprintf("%s (%s@%s)", tt.Name, tt.Version, tt.Channel), func() { 49 ts := e2e.New(suite.T(), false) 50 defer ts.Close() 51 52 // Determine URL of install script. 53 baseUrl := "https://state-tool.s3.amazonaws.com/update/state/" 54 scriptBaseName := "install." 55 if runtime.GOOS != "windows" { 56 scriptBaseName += "sh" 57 } else { 58 scriptBaseName += "ps1" 59 } 60 scriptUrl := baseUrl + constants.ChannelName + "/" + scriptBaseName 61 62 // Fetch it. 63 b, err := httputil.GetDirect(scriptUrl) 64 suite.Require().NoError(err) 65 script := filepath.Join(ts.Dirs.Work, scriptBaseName) 66 suite.Require().NoError(fileutils.WriteFile(script, b)) 67 68 // Construct installer command to execute. 69 installDir := filepath.Join(ts.Dirs.Work, "install") 70 argsPlain := []string{script} 71 argsPlain = append(argsPlain, "-t", installDir) 72 argsPlain = append(argsPlain, "-n") 73 if tt.Channel != "" { 74 argsPlain = append(argsPlain, "-b", tt.Channel) 75 } 76 if tt.Version != "" { 77 argsPlain = append(argsPlain, "-v", tt.Version) 78 } 79 80 argsWithActive := append(argsPlain, "-f") 81 if tt.Activate != "" { 82 argsWithActive = append(argsWithActive, "--activate", tt.Activate) 83 } 84 if tt.ActivateByCommand != "" { 85 cmd := fmt.Sprintf("state activate %s", tt.ActivateByCommand) 86 if runtime.GOOS == "windows" { 87 cmd = "'" + cmd + "'" 88 } 89 argsWithActive = append(argsWithActive, "-c", cmd) 90 } 91 92 // Make the directory to install to. 93 appInstallDir := filepath.Join(ts.Dirs.Work, "app") 94 suite.NoError(fileutils.Mkdir(appInstallDir)) 95 96 // Perform the installation. 97 cmd := "bash" 98 opts := []e2e.SpawnOptSetter{ 99 e2e.OptArgs(argsWithActive...), 100 e2e.OptAppendEnv(constants.DisableRuntime + "=false"), 101 e2e.OptAppendEnv(fmt.Sprintf("%s=%s", constants.AppInstallDirOverrideEnvVarName, appInstallDir)), 102 e2e.OptAppendEnv(fmt.Sprintf("%s=FOO", constants.OverrideSessionTokenEnvVarName)), 103 e2e.OptAppendEnv(fmt.Sprintf("%s=false", constants.DisableActivateEventsEnvVarName)), 104 } 105 if runtime.GOOS == "windows" { 106 cmd = "powershell.exe" 107 opts = append(opts, e2e.OptAppendEnv("SHELL=")) 108 } 109 cp := ts.SpawnCmdWithOpts(cmd, opts...) 110 cp.Expect("Preparing Installer for State Tool Package Manager") 111 cp.Expect("Installation Complete", e2e.RuntimeSourcingTimeoutOpt) 112 113 if tt.Activate != "" || tt.ActivateByCommand != "" { 114 cp.Expect("Creating a Virtual Environment") 115 cp.Expect("Quick Start", e2e.RuntimeSourcingTimeoutOpt) 116 // ensure that shell is functional 117 cp.ExpectInput() 118 119 cp.SendLine("python3 -c \"import sys; print(sys.copyright)\"") 120 cp.Expect("ActiveState") 121 } 122 123 cp.SendLine("state --version") 124 cp.Expect("Version " + constants.Version) 125 cp.Expect("Channel " + constants.ChannelName) 126 cp.Expect("Built") 127 cp.SendLine("exit") 128 129 cp.ExpectExitCode(0) 130 131 stateExec, err := installation.StateExecFromDir(installDir) 132 suite.NoError(err) 133 suite.FileExists(stateExec) 134 135 suite.assertBinDirContents(filepath.Join(installDir, "bin")) 136 suite.assertCorrectVersion(ts, installDir, tt.Version, tt.Channel) 137 suite.assertAnalytics(ts) 138 suite.DirExists(ts.Dirs.Config) 139 140 // Verify that can install overtop 141 if runtime.GOOS != "windows" { 142 cp = ts.SpawnCmdWithOpts("bash", e2e.OptArgs(argsPlain...)) 143 } else { 144 cp = ts.SpawnCmdWithOpts("powershell.exe", e2e.OptArgs(argsPlain...), 145 e2e.OptAppendEnv("SHELL="), 146 ) 147 } 148 cp.Expect("successfully installed") 149 cp.ExpectInput() 150 cp.SendLine("exit") 151 cp.ExpectExitCode(0) 152 if runtime.GOOS == "windows" { 153 ts.IgnoreLogErrors() // Follow-up DX-2678 154 } 155 }) 156 } 157 } 158 159 func (suite *InstallScriptsIntegrationTestSuite) TestInstall_NonEmptyTarget() { 160 suite.OnlyRunForTags(tagsuite.InstallScripts) 161 ts := e2e.New(suite.T(), false) 162 defer ts.Close() 163 164 script := scriptPath(suite.T(), ts.Dirs.Work) 165 argsPlain := []string{script, "-t", ts.Dirs.Work, "-n"} 166 argsPlain = append(argsPlain, "-b", constants.ChannelName) 167 var cp *e2e.SpawnedCmd 168 if runtime.GOOS != "windows" { 169 cp = ts.SpawnCmdWithOpts("bash", e2e.OptArgs(argsPlain...)) 170 } else { 171 cp = ts.SpawnCmdWithOpts("powershell.exe", e2e.OptArgs(argsPlain...), e2e.OptAppendEnv("SHELL=")) 172 } 173 cp.Expect("Installation path must be an empty directory") 174 175 // Originally this was ExpectExitCode(1), but particularly on Windows this turned out to be unreliable. Probably 176 // because of powershell. 177 // Since we asserted the expected error above we don't need to go on a wild goose chase here. 178 cp.ExpectExit() 179 ts.IgnoreLogErrors() 180 } 181 182 func (suite *InstallScriptsIntegrationTestSuite) TestInstall_VersionDoesNotExist() { 183 suite.OnlyRunForTags(tagsuite.InstallScripts) 184 ts := e2e.New(suite.T(), false) 185 defer ts.Close() 186 187 script := scriptPath(suite.T(), ts.Dirs.Work) 188 args := []string{script, "-t", ts.Dirs.Work, "-n"} 189 args = append(args, "-v", "does-not-exist") 190 var cp *e2e.SpawnedCmd 191 if runtime.GOOS != "windows" { 192 cp = ts.SpawnCmdWithOpts("bash", e2e.OptArgs(args...)) 193 } else { 194 cp = ts.SpawnCmdWithOpts("powershell.exe", e2e.OptArgs(args...), e2e.OptAppendEnv("SHELL=")) 195 } 196 if !condition.OnCI() || runtime.GOOS == "windows" { 197 // For some reason on Linux and macOS, there is no terminal output on CI. It works locally though. 198 cp.Expect("Could not download") 199 } 200 cp.ExpectExitCode(1) 201 ts.IgnoreLogErrors() 202 } 203 204 // scriptPath returns the path to an installation script copied to targetDir, if useTestUrl is true, the install script is modified to download from the local test server instead 205 func scriptPath(t *testing.T, targetDir string) string { 206 ext := ".ps1" 207 if runtime.GOOS != "windows" { 208 ext = ".sh" 209 } 210 name := "install" + ext 211 root := environment.GetRootPathUnsafe() 212 subdir := "installers" 213 214 source := filepath.Join(root, subdir, name) 215 if !fileutils.FileExists(source) { 216 t.Fatalf("Could not find install script %s", source) 217 } 218 219 target := filepath.Join(targetDir, filepath.Base(source)) 220 err := fileutils.CopyFile(source, target) 221 require.NoError(t, err) 222 223 return target 224 } 225 226 // assertBinDirContents checks if given files are or are not in the bin directory 227 func (suite *InstallScriptsIntegrationTestSuite) assertBinDirContents(dir string) { 228 binFiles := suite.listFilesOnly(dir) 229 suite.Contains(binFiles, "state"+osutils.ExeExtension) 230 suite.Contains(binFiles, "state-svc"+osutils.ExeExtension) 231 } 232 233 // listFilesOnly is a helper function for assertBinDirContents filtering a directory recursively for base filenames 234 // It allows for simple and coarse checks if a file exists or does not exist in the directory structure 235 func (suite *InstallScriptsIntegrationTestSuite) listFilesOnly(dir string) []string { 236 files, err := fileutils.ListDirSimple(dir, true) 237 suite.Require().NoError(err) 238 files = funk.Filter(files, func(f string) bool { 239 return !fileutils.IsDir(f) 240 }).([]string) 241 return funk.Map(files, filepath.Base).([]string) 242 } 243 244 func (suite *InstallScriptsIntegrationTestSuite) assertCorrectVersion(ts *e2e.Session, installDir, expectedVersion, expectedChannel string) { 245 type versionData struct { 246 Version string `json:"version"` 247 Channel string `json:"channel"` 248 } 249 250 stateExec, err := installation.StateExecFromDir(installDir) 251 suite.NoError(err) 252 253 cp := ts.SpawnCmd(stateExec, "--version", "--output=json") 254 cp.ExpectExitCode(0) 255 actual := versionData{} 256 out := cp.StrippedSnapshot() 257 suite.Require().NoError(json.Unmarshal([]byte(out), &actual)) 258 259 if expectedVersion != "" { 260 suite.Equal(expectedVersion, actual.Version) 261 } 262 if expectedChannel != "" { 263 suite.Equal(expectedChannel, actual.Channel) 264 } 265 } 266 267 func (suite *InstallScriptsIntegrationTestSuite) assertAnalytics(ts *e2e.Session) { 268 // Verify analytics reported a non-empty sessionToken. 269 sessionTokenFound := false 270 events := parseAnalyticsEvents(suite, ts) 271 suite.Require().NotEmpty(events) 272 for _, event := range events { 273 if event.Category == anaConst.CatInstallerFunnel && event.Dimensions != nil { 274 suite.Assert().NotEmpty(*event.Dimensions.SessionToken) 275 sessionTokenFound = true 276 break 277 } 278 } 279 suite.Assert().True(sessionTokenFound, "sessionToken was not found in analytics") 280 } 281 282 func TestInstallScriptsIntegrationTestSuite(t *testing.T) { 283 suite.Run(t, new(InstallScriptsIntegrationTestSuite)) 284 }