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  }