github.com/symfony-cli/symfony-cli@v0.0.0-20240514161054-ece2df437dfa/local/php/executor_test.go (about) 1 /* 2 * Copyright (c) 2021-present Fabien Potencier <fabien@symfony.com> 3 * 4 * This file is part of Symfony CLI project 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU Affero General Public License as 8 * published by the Free Software Foundation, either version 3 of the 9 * License, or (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU Affero General Public License for more details. 15 * 16 * You should have received a copy of the GNU Affero General Public License 17 * along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package php 21 22 import ( 23 "bytes" 24 "fmt" 25 "io" 26 "os" 27 "os/exec" 28 "path/filepath" 29 "strconv" 30 "strings" 31 "testing" 32 33 "github.com/mitchellh/go-homedir" 34 . "gopkg.in/check.v1" 35 ) 36 37 type ExecutorSuite struct{} 38 39 var _ = Suite(&ExecutorSuite{}) 40 41 // Modify os.Stdout to write to the given buffer. 42 func testStdoutCapture(c *C, dst io.Writer) func() { 43 r, w, err := os.Pipe() 44 if err != nil { 45 c.Fatalf("err: %s", err) 46 } 47 48 // Modify stdout 49 old := os.Stdout 50 os.Stdout = w 51 52 // Copy 53 doneCh := make(chan struct{}) 54 go func() { 55 defer close(doneCh) 56 defer r.Close() 57 io.Copy(dst, r) 58 }() 59 60 return func() { 61 // Close the writer end of the pipe 62 w.Sync() 63 w.Close() 64 65 // Reset stdout 66 os.Stdout = old 67 68 // Wait for the data copy to complete to avoid a race reading data 69 <-doneCh 70 } 71 } 72 73 func restoreExecCommand() { 74 execCommand = exec.Command 75 } 76 77 func fakeExecCommand(cmd string, args ...string) { 78 execCommand = func(string, ...string) *exec.Cmd { 79 cs := []string{"-test.run=TestHelperProcess", "--", cmd} 80 cs = append(cs, args...) 81 cmd := exec.Command(os.Args[0], cs...) 82 cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} 83 // Set the working directory right now so that it can be changed by 84 // calling test case 85 cmd.Dir, _ = os.Getwd() 86 return cmd 87 } 88 } 89 90 func TestHelperProcess(t *testing.T) { 91 if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { 92 return 93 } 94 os.Unsetenv("GO_WANT_HELPER_PROCESS") 95 96 switch os.Args[3] { 97 case "dump-env": 98 fmt.Println(os.Getwd()) 99 for _, v := range os.Environ() { 100 fmt.Println(v) 101 } 102 case "exit-code": 103 code, _ := strconv.Atoi(os.Args[4]) 104 os.Exit(code) 105 } 106 os.Exit(0) 107 } 108 109 func (s *ExecutorSuite) TestNotEnoughArgs(c *C) { 110 defer cleanupExecutorTempFiles() 111 112 c.Assert((&Executor{BinName: "php"}).Execute(true), Equals, 1) 113 } 114 115 func (s *ExecutorSuite) TestForwardExitCode(c *C) { 116 defer restoreExecCommand() 117 fakeExecCommand("exit-code", "5") 118 119 home, err := filepath.Abs("testdata/executor") 120 c.Assert(err, IsNil) 121 122 homedir.Reset() 123 os.Setenv("HOME", home) 124 defer homedir.Reset() 125 126 oldwd, _ := os.Getwd() 127 defer os.Chdir(oldwd) 128 os.Chdir(filepath.Join(home, "project")) 129 defer cleanupExecutorTempFiles() 130 131 c.Assert((&Executor{BinName: "php", Args: []string{"php"}}).Execute(true), Equals, 5) 132 } 133 134 func (s *ExecutorSuite) TestEnvInjection(c *C) { 135 defer restoreExecCommand() 136 fakeExecCommand("dump-env") 137 138 home, err := filepath.Abs("testdata/executor") 139 c.Assert(err, IsNil) 140 141 homedir.Reset() 142 os.Setenv("HOME", home) 143 defer homedir.Reset() 144 145 oldwd, _ := os.Getwd() 146 defer os.Chdir(oldwd) 147 os.Chdir(filepath.Join(home, "project")) 148 149 os.Rename("git", ".git") 150 defer os.Rename(".git", "git") 151 defer cleanupExecutorTempFiles() 152 153 var output bytes.Buffer 154 outCloser := testStdoutCapture(c, &output) 155 c.Assert((&Executor{BinName: "php", Args: []string{"php"}}).Execute(true), Equals, 0) 156 outCloser() 157 // Nothing should be injected by default as tunnel is not open 158 c.Check(false, Equals, strings.Contains(output.String(), "DATABASE_URL=pgsql://127.0.0.1:30000")) 159 // But .env should be properly loaded 160 c.Check(true, Equals, strings.Contains(output.String(), "USER_DEFINED_ENVVAR=foobar")) 161 c.Check(true, Equals, strings.Contains(output.String(), "DATABASE_URL=mysql://127.0.0.1")) 162 // Checks local properly feed Symfony with SYMFONY_DOTENV_VARS 163 c.Check(true, Equals, strings.Contains(output.String(), "SYMFONY_DOTENV_VARS=DATABASE_URL,USER_DEFINED_ENVVAR") || strings.Contains(output.String(), "SYMFONY_DOTENV_VARS=USER_DEFINED_ENVVAR,DATABASE_URL")) 164 165 // change the project name to get exposed env vars 166 projectFile := filepath.Join(".platform", "local", "project.yaml") 167 contents, err := os.ReadFile(projectFile) 168 c.Assert(err, IsNil) 169 defer os.WriteFile(projectFile, contents, 0644) 170 os.WriteFile(projectFile, bytes.Replace(contents, []byte("bew7pfa7t2ut2"), []byte("aew7pfa7t2ut2"), 1), 0644) 171 172 output.Reset() 173 outCloser = testStdoutCapture(c, &output) 174 c.Assert((&Executor{BinName: "php", Args: []string{"php"}}).Execute(true), Equals, 0) 175 outCloser() 176 177 // Now overridden, check tunnel information is properly loaded 178 c.Check(true, Equals, strings.Contains(output.String(), "DATABASE_URL=postgres://main:main@127.0.0.1:30001/main?sslmode=disable&charset=utf8&serverVersion=13")) 179 // And checks .env keeps being properly loaded 180 c.Check(true, Equals, strings.Contains(output.String(), "USER_DEFINED_ENVVAR=foobar")) 181 // But do not override tunnel information 182 c.Check(false, Equals, strings.Contains(output.String(), "DATABASE_URL=mysql://127.0.0.1")) 183 // Checks local properly feed Symfony with SYMFONY_DOTENV_VARS 184 c.Check(true, Equals, strings.Contains(output.String(), "SYMFONY_DOTENV_VARS=USER_DEFINED_ENVVAR")) 185 } 186 187 func (s *PHPSuite) TestDetectScript(c *C) { 188 phpgo, err := filepath.Abs("php.go") 189 c.Assert(err, IsNil) 190 phpgo = filepath.Dir(phpgo) 191 tests := []struct { 192 args []string 193 expected string 194 }{ 195 {[]string{"php.go"}, phpgo}, 196 {[]string{"-fphp.go"}, phpgo}, 197 {[]string{"-f", "php.go"}, phpgo}, 198 {[]string{"-c", "php.ini", "php.go"}, phpgo}, 199 {[]string{"-c", "php.ini", "-f", "php.go"}, phpgo}, 200 {[]string{"-c", "php.ini", "-fphp.go"}, phpgo}, 201 {[]string{"-cphp.ini", "php.go"}, phpgo}, 202 {[]string{"-l", "php.go"}, phpgo}, 203 {[]string{"-cphp.ini", "-l", "-z", "foo", "php.go"}, phpgo}, 204 {[]string{"php.go", "--", "foo.go"}, phpgo}, 205 } 206 for _, test := range tests { 207 c.Assert(detectScriptDir(test.args), Equals, test.expected) 208 } 209 } 210 211 func cleanupExecutorTempFiles() { 212 os.RemoveAll("testdata/executor/.symfony5/tmp") 213 os.RemoveAll("testdata/executor/.symfony5/var") 214 }