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  }