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  }