get.porter.sh/porter@v1.3.0/tests/tester/main.go (about) 1 package tester 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io" 8 "os" 9 "path/filepath" 10 "strings" 11 "testing" 12 13 "get.porter.sh/porter/pkg/portercontext" 14 "get.porter.sh/porter/pkg/storage/plugins/mongodb_docker" 15 "get.porter.sh/porter/tests" 16 "github.com/stretchr/testify/require" 17 "github.com/uwu-tools/magex/shx" 18 ) 19 20 type Tester struct { 21 originalPwd string 22 23 // unique database name assigned to the test 24 dbName string 25 26 // TestContext is a porter context for the filesystem. 27 TestContext *portercontext.TestContext 28 29 // TestDir is the temp directory created for the test. 30 TestDir string 31 32 // PorterHomeDir is the temp PORTER_HOME directory for the test. 33 PorterHomeDir string 34 35 // RepoRoot is the root of the porter repository. 36 // Useful for constructing paths that won't break when the test is moved. 37 RepoRoot string 38 39 // T is the test helper. 40 T *testing.T 41 } 42 43 // NewTest sets up for a smoke test. 44 // 45 // Always defer Tester.Close(), even when an error is returned. 46 func NewTest(t *testing.T) (Tester, error) { 47 return NewTestWithConfig(t, "") 48 } 49 50 // NewTestWithConfig sets up for a smoke test using the specified 51 // Porter config file. The path should be either be absolute, or 52 // relative to the repository root. 53 // 54 // Always defer Tester.Close(), even when an error is returned. 55 func NewTestWithConfig(t *testing.T, configFilePath string) (Tester, error) { 56 var err error 57 pwd, _ := os.Getwd() 58 test := &Tester{T: t, originalPwd: pwd} 59 60 test.TestContext = portercontext.NewTestContext(t) 61 test.TestContext.UseFilesystem() 62 test.RepoRoot = test.TestContext.FindRepoRoot() 63 64 test.TestDir, err = os.MkdirTemp("", "porter-test") 65 if err != nil { 66 return *test, fmt.Errorf("could not create temp test directory: %w", err) 67 } 68 69 test.dbName = tests.GenerateDatabaseName(t.Name()) 70 71 err = test.createPorterHome(configFilePath) 72 if err != nil { 73 return *test, err 74 } 75 76 os.Setenv("PORTER_HOME", test.PorterHomeDir) 77 os.Setenv("PATH", test.PorterHomeDir+string(os.PathListSeparator)+os.Getenv("PATH")) 78 79 return *test, test.startMongo(context.Background()) 80 } 81 82 // CurrentNamespace configured in Porter's config file 83 func (t Tester) CurrentNamespace() string { 84 return "dev" 85 } 86 87 func (t Tester) startMongo(ctx context.Context) error { 88 conn, err := mongodb_docker.EnsureMongoIsRunning(ctx, 89 t.TestContext.Context, 90 "porter-smoke-test-mongodb-plugin", 91 "27017", 92 "", 93 t.dbName, 94 10, 95 ) 96 if err != nil { 97 return err 98 } 99 defer conn.Close() 100 101 // Start with a fresh database 102 err = conn.RemoveDatabase(ctx) 103 return err 104 } 105 106 // Run a porter command and fail the test if the command returns an error. 107 func (t Tester) RequirePorter(args ...string) (stdout string, combinedoutput string) { 108 t.T.Helper() 109 stdout, combinedoutput, err := t.RunPorter(args...) 110 if err != nil { 111 t.T.Logf("failed to run porter %s", strings.Join(args, " ")) 112 t.T.Log(combinedoutput) 113 } 114 require.NoError(t.T, err) 115 return stdout, combinedoutput 116 } 117 118 // RunPorter executes a porter command returning stderr when it fails. 119 func (t Tester) RunPorter(args ...string) (stdout string, combinedoutput string, err error) { 120 t.T.Helper() 121 return t.RunPorterWith(func(cmd *shx.PreparedCommand) { 122 cmd.Args(args...) 123 }) 124 } 125 126 // RunPorterWith works like RunPorter, but you can customize the command before it's run. 127 func (t Tester) RunPorterWith(opts ...func(*shx.PreparedCommand)) (stdout string, combinedoutput string, err error) { 128 t.T.Helper() 129 130 cmd := t.buildPorterCommand(opts...) 131 132 // Copy stderr to stdout so we can return the "full" output printed to the console 133 stdoutBuf := &bytes.Buffer{} 134 stderrBuf := &bytes.Buffer{} 135 output := &bytes.Buffer{} 136 cmd.Stdout(io.MultiWriter(stdoutBuf, output)).Stderr(io.MultiWriter(stderrBuf, output)) 137 138 t.T.Log(cmd.String()) 139 ran, _, err := cmd.Exec() 140 if err != nil { 141 if ran { 142 err = fmt.Errorf("%s: %w", stderrBuf.String(), err) 143 } 144 return stdoutBuf.String(), output.String(), err 145 } 146 return stdoutBuf.String(), output.String(), nil 147 } 148 149 // Build a porter command, ready to be executed or further customized. 150 func (t Tester) buildPorterCommand(opts ...func(*shx.PreparedCommand)) shx.PreparedCommand { 151 debugCmdPrefix := os.Getenv("PORTER_RUN_IN_DEBUGGER") 152 153 configureCommand := func(cmd shx.PreparedCommand) { 154 cmd.Env("PORTER_HOME="+t.PorterHomeDir, "PORTER_TEST_DB_NAME="+t.dbName, "PORTER_VERBOSITY=debug") 155 for _, opt := range opts { 156 opt(&cmd) 157 } 158 } 159 160 cmd := shx.Command("porter") 161 configureCommand(cmd) 162 163 prettyCmd := cmd.String() 164 if debugCmdPrefix != "" && strings.HasPrefix(prettyCmd, debugCmdPrefix) { 165 port := os.Getenv("PORTER_DEBUGGER_PORT") 166 if port == "" { 167 port = "55942" 168 } 169 porterPath := filepath.Join(t.RepoRoot, "bin/porter") 170 cmd = shx.Command("dlv", "exec", porterPath, "--listen=:"+port, "--headless=true", "--api-version=2", "--accept-multiclient", "--") 171 configureCommand(cmd) 172 } 173 174 return cmd 175 } 176 177 func (t Tester) Close() { 178 t.T.Log("Removing temp test PORTER_HOME") 179 os.RemoveAll(t.PorterHomeDir) 180 181 t.T.Log("Removing temp test directory") 182 os.RemoveAll(t.TestDir) 183 184 // Reset the current directory for the next test 185 t.Chdir(t.originalPwd) 186 } 187 188 // Create a test PORTER_HOME directory with the optional config file. 189 // The config file path should be specified relative to the repository root 190 func (t *Tester) createPorterHome(configFilePath string) error { 191 if configFilePath == "" { 192 configFilePath = "tests/testdata/config/config.yaml" 193 } 194 if !filepath.IsAbs(configFilePath) { 195 configFilePath = filepath.Join(t.RepoRoot, configFilePath) 196 } 197 198 var err error 199 binDir := filepath.Join(t.RepoRoot, "bin") 200 t.PorterHomeDir, err = os.MkdirTemp("", "porter") 201 if err != nil { 202 return fmt.Errorf("could not create temp PORTER_HOME directory: %w", err) 203 } 204 205 require.NoError(t.T, shx.Copy(filepath.Join(binDir, "porter*"), t.PorterHomeDir), 206 "could not copy porter binaries into test PORTER_HOME") 207 require.NoError(t.T, shx.Copy(filepath.Join(binDir, "runtimes"), t.PorterHomeDir, shx.CopyRecursive), 208 "could not copy runtimes/ into test PORTER_HOME") 209 require.NoError(t.T, shx.Copy(filepath.Join(binDir, "mixins"), t.PorterHomeDir, shx.CopyRecursive), 210 "could not copy mixins/ into test PORTER_HOME") 211 require.NoError(t.T, shx.Copy(configFilePath, filepath.Join(t.PorterHomeDir, "config"+filepath.Ext(configFilePath))), 212 "error copying config file to PORTER_HOME") 213 214 return nil 215 } 216 217 func (t Tester) Chdir(dir string) { 218 t.TestContext.Chdir(dir) 219 require.NoError(t.T, os.Chdir(dir)) 220 }