github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/test/integration/run_int_test.go (about) 1 package integration 2 3 import ( 4 "fmt" 5 "os" 6 "os/exec" 7 "path/filepath" 8 "runtime" 9 "strings" 10 "testing" 11 "time" 12 13 "github.com/ActiveState/cli/internal/testhelpers/suite" 14 15 "github.com/ActiveState/termtest" 16 17 "github.com/ActiveState/cli/internal/constants" 18 "github.com/ActiveState/cli/internal/environment" 19 "github.com/ActiveState/cli/internal/fileutils" 20 "github.com/ActiveState/cli/internal/testhelpers/e2e" 21 "github.com/ActiveState/cli/internal/testhelpers/tagsuite" 22 "github.com/ActiveState/cli/pkg/project" 23 "github.com/ActiveState/cli/pkg/projectfile" 24 ) 25 26 type RunIntegrationTestSuite struct { 27 tagsuite.Suite 28 } 29 30 func (suite *RunIntegrationTestSuite) createProjectFile(ts *e2e.Session, pythonVersion int) { 31 root := environment.GetRootPathUnsafe() 32 interruptScript := filepath.Join(root, "test", "integration", "assets", "run", "interrupt.go") 33 err := fileutils.CopyFile(interruptScript, filepath.Join(ts.Dirs.Work, "interrupt.go")) 34 suite.Require().NoError(err) 35 36 // ActiveState-CLI/Python3 is just a place-holder that is never used 37 configFileContent := strings.TrimPrefix(fmt.Sprintf(` 38 project: https://platform.activestate.com/ActiveState-CLI/Python%d 39 scripts: 40 - name: test-interrupt 41 description: A script that sleeps for a very long time. It should be interrupted. The first interrupt does not terminate. 42 standalone: true 43 language: bash 44 value: | 45 go build -o ./interrupt ./interrupt.go 46 ./interrupt 47 if: ne .OS.Name "Windows" 48 - name: test-interrupt 49 description: A script that sleeps for a very long time. It should be interrupted. The first interrupt does not terminate. 50 standalone: true 51 language: bash 52 value: | 53 go build -o .\interrupt.exe .\interrupt.go 54 .\interrupt.exe 55 if: eq .OS.Name "Windows" 56 - name: helloWorld 57 value: echo "Hello World!" 58 standalone: true 59 if: ne .OS.Name "Windows" 60 - name: helloWorld 61 standalone: true 62 value: echo Hello World! 63 if: eq .OS.Name "Windows" 64 - name: testMultipleLanguages 65 value: | 66 import sys 67 print(sys.version) 68 language: python2,python3 69 - name: nonZeroExit 70 value: | 71 exit 123 72 standalone: true 73 language: bash 74 `, pythonVersion), "\n") 75 76 ts.PrepareActiveStateYAML(configFileContent) 77 ts.PrepareCommitIdFile("fbc613d6-b0b1-4f84-b26e-4aa5869c4e54") 78 } 79 80 func (suite *RunIntegrationTestSuite) SetupTest() { 81 if runtime.GOOS == "windows" { 82 if _, err := exec.LookPath("bash"); err != nil { 83 suite.T().Skip("This test requires a bash shell in your PATH") 84 } 85 } 86 } 87 88 func (suite *RunIntegrationTestSuite) TearDownTest() { 89 projectfile.Reset() 90 } 91 92 func (suite *RunIntegrationTestSuite) expectTerminateBatchJob(cp *e2e.SpawnedCmd) { 93 if runtime.GOOS == "windows" { 94 // send N to "Terminate batch job (Y/N)" question 95 cp.Expect("Terminate batch job") 96 time.Sleep(200 * time.Millisecond) 97 cp.SendLine("N") 98 cp.Expect("N", termtest.OptExpectTimeout(500*time.Millisecond)) 99 } 100 } 101 102 // TestActivatedEnv is a regression test for the following tickets: 103 // - https://www.pivotaltracker.com/story/show/167523128 104 // - https://www.pivotaltracker.com/story/show/169509213 105 func (suite *RunIntegrationTestSuite) TestInActivatedEnv() { 106 suite.OnlyRunForTags(tagsuite.Run, tagsuite.Activate, tagsuite.Interrupt) 107 if runtime.GOOS != "linux" && e2e.RunningOnCI() { 108 suite.T().Skip("Windows CI does not support ctrl-c events, mac CI has Golang build issues") 109 } 110 ts := e2e.New(suite.T(), false) 111 defer ts.Close() 112 113 suite.createProjectFile(ts, 3) 114 115 cp := ts.Spawn("activate") 116 cp.Expect("Activated", e2e.RuntimeSourcingTimeoutOpt) 117 cp.ExpectInput(termtest.OptExpectTimeout(10 * time.Second)) 118 119 cp.SendLine(fmt.Sprintf("%s run testMultipleLanguages", ts.Exe)) 120 cp.Expect("Operating on project") 121 cp.Expect("ActiveState-CLI/Python3") 122 cp.Expect("3") 123 124 cp.SendLine(fmt.Sprintf("%s run test-interrupt", cp.Executable())) 125 cp.Expect("Start of script", termtest.OptExpectTimeout(10*time.Second)) 126 cp.SendCtrlC() 127 cp.Expect("received interrupt", termtest.OptExpectTimeout(5*time.Second)) 128 cp.Expect("After first sleep or interrupt", termtest.OptExpectTimeout(2*time.Second)) 129 cp.SendCtrlC() 130 suite.expectTerminateBatchJob(cp) 131 132 cp.SendLine("exit 0") 133 cp.ExpectExitCode(0) 134 suite.Require().NotContains( 135 cp.Output(), "not printed after second interrupt", 136 ) 137 } 138 139 // tests that convenience commands for activestate.yaml scripts are available 140 // in bash subshells from the activated state 141 func (suite *RunIntegrationTestSuite) TestScriptBashSubshell() { 142 if runtime.GOOS == "windows" { 143 suite.T().Skip("bash subshells are not supported by our tests on windows") 144 } 145 146 suite.OnlyRunForTags(tagsuite.Run) 147 ts := e2e.New(suite.T(), false) 148 defer ts.Close() 149 150 suite.createProjectFile(ts, 3) 151 152 cp := ts.SpawnWithOpts(e2e.OptArgs("activate"), e2e.OptAppendEnv("SHELL=bash")) 153 cp.Expect("Activated", e2e.RuntimeSourcingTimeoutOpt) 154 cp.ExpectInput(termtest.OptExpectTimeout(10 * time.Second)) 155 156 cp.SendLine("helloWorld") 157 cp.Expect("Hello World!") 158 cp.SendLine("bash -c helloWorld") 159 cp.Expect("Hello World!") 160 cp.SendLine("exit") 161 cp.ExpectExitCode(0) 162 } 163 164 func (suite *RunIntegrationTestSuite) TestOneInterrupt() { 165 suite.OnlyRunForTags(tagsuite.Run, tagsuite.Interrupt, tagsuite.Critical) 166 if runtime.GOOS == "windows" && e2e.RunningOnCI() { 167 suite.T().Skip("Windows CI does not support ctrl-c events") 168 } 169 ts := e2e.New(suite.T(), false) 170 defer ts.Close() 171 suite.createProjectFile(ts, 3) 172 173 cp := ts.Spawn("run", "test-interrupt") 174 cp.Expect("Start of script") 175 // interrupt the first (very long) sleep 176 cp.SendCtrlC() 177 178 cp.Expect("received interrupt", termtest.OptExpectTimeout(3*time.Second)) 179 cp.Expect("After first sleep or interrupt", termtest.OptExpectTimeout(2*time.Second)) 180 cp.Expect("After second sleep") 181 suite.expectTerminateBatchJob(cp) 182 cp.ExpectExitCode(0) 183 } 184 185 func (suite *RunIntegrationTestSuite) TestTwoInterrupts() { 186 suite.OnlyRunForTags(tagsuite.Run, tagsuite.Interrupt) 187 if runtime.GOOS == "windows" && e2e.RunningOnCI() { 188 suite.T().Skip("Windows CI does not support ctrl-c events") 189 } 190 ts := e2e.New(suite.T(), false) 191 defer ts.Close() 192 suite.createProjectFile(ts, 3) 193 194 ts.LoginAsPersistentUser() 195 defer ts.LogoutUser() 196 197 cp := ts.Spawn("run", "test-interrupt") 198 cp.Expect("Start of script") 199 cp.SendCtrlC() 200 cp.Expect("received interrupt", termtest.OptExpectTimeout(3*time.Second)) 201 cp.Expect("After first sleep or interrupt", termtest.OptExpectTimeout(2*time.Second)) 202 cp.SendCtrlC() 203 suite.expectTerminateBatchJob(cp) 204 cp.ExpectExitCode(123) 205 ts.IgnoreLogErrors() 206 suite.Require().NotContains( 207 cp.Output(), "not printed after second interrupt", 208 ) 209 } 210 211 func (suite *RunIntegrationTestSuite) TestRun_Help() { 212 suite.OnlyRunForTags(tagsuite.Run) 213 ts := e2e.New(suite.T(), false) 214 defer ts.Close() 215 suite.createProjectFile(ts, 3) 216 217 cp := ts.Spawn("run", "-h") 218 cp.Expect("Usage") 219 cp.Expect("Arguments") 220 cp.ExpectExitCode(0) 221 } 222 223 func (suite *RunIntegrationTestSuite) TestRun_ExitCode() { 224 suite.OnlyRunForTags(tagsuite.Run, tagsuite.ExitCode) 225 ts := e2e.New(suite.T(), false) 226 defer ts.Close() 227 suite.createProjectFile(ts, 3) 228 229 cp := ts.Spawn("run", "nonZeroExit") 230 cp.ExpectExitCode(123) 231 } 232 233 func (suite *RunIntegrationTestSuite) TestRun_Unauthenticated() { 234 suite.OnlyRunForTags(tagsuite.Run) 235 ts := e2e.New(suite.T(), false) 236 defer ts.Close() 237 238 suite.createProjectFile(ts, 2) 239 240 cp := ts.SpawnWithOpts( 241 e2e.OptArgs("activate"), 242 e2e.OptAppendEnv(constants.DisableRuntime+"=false"), 243 ) 244 cp.Expect("Activated", e2e.RuntimeSourcingTimeoutOpt) 245 cp.ExpectInput(termtest.OptExpectTimeout(10 * time.Second)) 246 247 cp.SendLine(fmt.Sprintf("%s run testMultipleLanguages", cp.Executable())) 248 cp.Expect("2") 249 cp.ExpectInput(termtest.OptExpectTimeout(120 * time.Second)) 250 251 cp.SendLine("exit") 252 cp.ExpectExitCode(0) 253 } 254 255 func (suite *RunIntegrationTestSuite) TestRun_DeprecatedLackingLanguage() { 256 suite.OnlyRunForTags(tagsuite.Run) 257 ts := e2e.New(suite.T(), false) 258 defer ts.Close() 259 260 suite.createProjectFile(ts, 3) 261 262 cp := ts.Spawn("run", "helloWorld") 263 cp.Expect("Deprecation Warning", termtest.OptExpectTimeout(5*time.Second)) 264 cp.Expect("Hello", termtest.OptExpectTimeout(5*time.Second)) 265 } 266 267 func (suite *RunIntegrationTestSuite) TestRun_BadLanguage() { 268 suite.OnlyRunForTags(tagsuite.Run) 269 ts := e2e.New(suite.T(), false) 270 defer ts.Close() 271 272 suite.createProjectFile(ts, 3) 273 274 asyFilename := filepath.Join(ts.Dirs.Work, "activestate.yaml") 275 asyFile, err := os.OpenFile(asyFilename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 276 suite.Require().NoError(err, "config is opened for appending") 277 defer asyFile.Close() 278 279 _, err = asyFile.WriteString(strings.TrimPrefix(` 280 - name: badLanguage 281 language: bax 282 value: echo "shouldn't show" 283 `, "\n")) 284 suite.Require().NoError(err, "extra config is appended") 285 286 cp := ts.Spawn("run", "badLanguage") 287 cp.Expect("The language for this script is not supported", termtest.OptExpectTimeout(5*time.Second)) 288 } 289 290 func (suite *RunIntegrationTestSuite) TestRun_Perl_Variable() { 291 if runtime.GOOS == "windows" { 292 suite.T().Skip("Testing exec of Perl with variables is not applicable on Windows") 293 } 294 295 suite.OnlyRunForTags(tagsuite.Run) 296 ts := e2e.New(suite.T(), false) 297 defer ts.Close() 298 299 ts.PrepareProject("ActiveState-CLI/Perl-5.32", "a4762408-def6-41e4-b709-4cb548765005") 300 301 cp := ts.SpawnWithOpts( 302 e2e.OptArgs("activate"), 303 e2e.OptAppendEnv( 304 constants.DisableRuntime+"=false", 305 "PERL_VERSION=does_not_exist", 306 ), 307 ) 308 cp.Expect("Activated", e2e.RuntimeSourcingTimeoutOpt) 309 cp.ExpectInput(termtest.OptExpectTimeout(10 * time.Second)) 310 311 cp.SendLine("perl -MEnglish -e 'print $PERL_VERSION'") 312 cp.Expect("v5.32.0") 313 cp.SendLine("exit") 314 cp.ExpectExitCode(0) 315 } 316 317 func (suite *RunIntegrationTestSuite) TestRun_Args() { 318 suite.OnlyRunForTags(tagsuite.Run) 319 ts := e2e.New(suite.T(), false) 320 defer ts.Close() 321 322 suite.createProjectFile(ts, 3) 323 324 asyFilename := filepath.Join(ts.Dirs.Work, "activestate.yaml") 325 asyFile, err := os.OpenFile(asyFilename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 326 suite.Require().NoError(err, "config is opened for appending") 327 defer asyFile.Close() 328 329 lang := project.DefaultScriptLanguage()[0].String() 330 cmd := `if [ "$1" = "<3" ]; then echo heart; fi` 331 if runtime.GOOS == "windows" { 332 cmd = `@echo off 333 if "%1"=="<3" (echo heart)` // need to match indent of YAML below 334 } 335 _, err = asyFile.WriteString(strings.TrimPrefix(fmt.Sprintf(` 336 - name: args 337 language: %s 338 value: | 339 %s 340 `, lang, cmd), "\n")) 341 suite.Require().NoError(err, "extra config is appended") 342 343 arg := "<3" 344 if runtime.GOOS == "windows" { 345 // The '<' needs to be escaped with '^', and I don't know why. There is no way around it. 346 // The other exec and shell integration tests that test arg passing do not need this escape. 347 // Only this batch test does. 348 arg = "^<3" 349 } 350 cp := ts.Spawn("run", "args", arg) 351 cp.Expect("heart", termtest.OptExpectTimeout(5*time.Second)) 352 } 353 354 func TestRunIntegrationTestSuite(t *testing.T) { 355 suite.Run(t, new(RunIntegrationTestSuite)) 356 }