get.porter.sh/porter@v1.3.0/pkg/exec/builder/execute_test.go (about)

     1  package builder
     2  
     3  import (
     4  	"context"
     5  	"os"
     6  	"path/filepath"
     7  	"runtime"
     8  	"strings"
     9  	"testing"
    10  
    11  	"get.porter.sh/porter/pkg"
    12  	porterruntime "get.porter.sh/porter/pkg/runtime"
    13  	"get.porter.sh/porter/pkg/test"
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  )
    17  
    18  type TestAction struct {
    19  	Steps []TestStep
    20  }
    21  
    22  func (a TestAction) GetSteps() []ExecutableStep {
    23  	steps := make([]ExecutableStep, len(a.Steps))
    24  	for i := range a.Steps {
    25  		steps[i] = a.Steps[i]
    26  	}
    27  	return steps
    28  }
    29  
    30  func TestMain(m *testing.M) {
    31  	test.TestMainWithMockedCommandHandlers(m)
    32  }
    33  
    34  func TestExecuteSingleStepAction(t *testing.T) {
    35  	ctx := context.Background()
    36  	c := porterruntime.NewTestRuntimeConfig(t)
    37  
    38  	err := c.FileSystem.WriteFile("config.txt", []byte("abc123"), pkg.FileModeWritable)
    39  	require.NoError(t, err)
    40  
    41  	a := TestAction{
    42  		Steps: []TestStep{
    43  			{
    44  				Command: "foo",
    45  				Outputs: []Output{
    46  					TestFileOutput{Name: "file", FilePath: "config.txt"},
    47  					TestJsonPathOutput{Name: "jsonpath", JsonPath: "$.*"},
    48  					TestRegexOutput{Name: "regex", Regex: "(.*)"},
    49  				}},
    50  		},
    51  	}
    52  
    53  	c.Setenv(test.ExpectedCommandEnv, "foo")
    54  
    55  	_, err = ExecuteSingleStepAction(ctx, c.RuntimeConfig, a)
    56  	require.NoError(t, err, "ExecuteSingleStepAction should not have returned an error")
    57  
    58  	exists, _ := c.FileSystem.Exists("/cnab/app/porter/outputs/file")
    59  	assert.True(t, exists, "file output was not evaluated")
    60  
    61  	exists, _ = c.FileSystem.Exists("/cnab/app/porter/outputs/regex")
    62  	assert.True(t, exists, "regex output was not evaluated")
    63  
    64  	exists, _ = c.FileSystem.Exists("/cnab/app/porter/outputs/jsonpath")
    65  	assert.True(t, exists, "jsonpath output was not evaluated")
    66  }
    67  
    68  func Test_splitCommand(t *testing.T) {
    69  	t.Run("split space", func(t *testing.T) {
    70  		result := splitCommand([]string{"cmd", "--myarg", "val1 val2"})
    71  		assert.Equal(t, []string{"cmd", "--myarg", "val1", "val2"}, result, "strings not enclosed should be split apart")
    72  	})
    73  
    74  	t.Run("split tab", func(t *testing.T) {
    75  		result := splitCommand([]string{"cmd", "--myarg", "val1\tval2"})
    76  		assert.Equal(t, []string{"cmd", "--myarg", "val1", "val2"}, result, "strings not enclosed should be split apart")
    77  	})
    78  
    79  	t.Run("split newline", func(t *testing.T) {
    80  		result := splitCommand([]string{"cmd", "--myarg", "val1\nval2"})
    81  		assert.Equal(t, []string{"cmd", "--myarg", "val1", "val2"}, result, "strings not enclosed should be split apart")
    82  	})
    83  
    84  	t.Run("keep double quoted whitespace", func(t *testing.T) {
    85  		result := splitCommand([]string{"cmd", "--myarg", `"val1 val2" val3`})
    86  		assert.Equal(t, []string{"cmd", "--myarg", "val1 val2", "val3"}, result, "strings in the enclosing quotes should be grouped together")
    87  	})
    88  
    89  	t.Run("embedded single quote", func(t *testing.T) {
    90  		result := splitCommand([]string{"cmd", "--myarg", `"Patty O'Brien" true`})
    91  		assert.Equal(t, []string{"cmd", "--myarg", "Patty O'Brien", "true"}, result, "single quotes should be included in the enclosing quotes")
    92  	})
    93  
    94  	t.Run("escaped double quotes", func(t *testing.T) {
    95  		result := splitCommand([]string{"c", `"echo { \"test\": \"myvalue\" }"`})
    96  		assert.Equal(t, []string{"c", `echo { \"test\": \"myvalue\" }`}, result, "escaped double quotes should be included in the enclosing quotes")
    97  	})
    98  
    99  	t.Run("escaped single quotes", func(t *testing.T) {
   100  		result := splitCommand([]string{"c", `"echo $'I\'m a linux admin.'"`})
   101  		assert.Equal(t, []string{"c", `echo $'I\'m a linux admin.'`}, result, "escaped single quotes should be included in the enclosing quotes")
   102  	})
   103  
   104  	t.Run("unmatched double quote", func(t *testing.T) {
   105  		result := splitCommand([]string{"cmd", "--myarg", `"Patty O"Brien" true`})
   106  		assert.Equal(t, []string{"cmd", "--myarg", `"Patty O"Brien" true`}, result, "unmatched double quotes should cause the grouping to fail")
   107  	})
   108  
   109  	t.Run("unmatched single quote", func(t *testing.T) {
   110  		result := splitCommand([]string{"cmd", "--myarg", `'Patty O'Brien' true`})
   111  		assert.Equal(t, []string{"cmd", "--myarg", `'Patty O'Brien' true`}, result, "unmatched single quotes should cause the grouping to fail")
   112  	})
   113  }
   114  
   115  var _ HasOrderedArguments = TestOrderedStep{}
   116  
   117  type TestOrderedStep struct {
   118  	TestStep
   119  	SuffixArguments []string
   120  }
   121  
   122  func (s TestOrderedStep) GetSuffixArguments() []string {
   123  	return s.SuffixArguments
   124  }
   125  
   126  func TestExecuteStep_HasOrderedArguments(t *testing.T) {
   127  	ctx := context.Background()
   128  	c := porterruntime.NewTestRuntimeConfig(t)
   129  	step := TestOrderedStep{
   130  		TestStep: TestStep{
   131  			Command:   "docker",
   132  			Arguments: []string{"build"},
   133  			Flags: []Flag{
   134  				NewFlag("t", "getporter/porter-hello:latest"),
   135  			},
   136  		},
   137  		SuffixArguments: []string{"."},
   138  	}
   139  
   140  	c.Setenv(test.ExpectedCommandEnv, "docker build -t getporter/porter-hello:latest .")
   141  
   142  	_, err := ExecuteStep(ctx, c.RuntimeConfig, step)
   143  	require.NoError(t, err, "ExecuteStep failed")
   144  }
   145  
   146  func TestExecuteStep_SpecifiesCustomWorkingDirectory(t *testing.T) {
   147  	ctx := context.Background()
   148  	c := porterruntime.NewTestRuntimeConfig(t)
   149  	c.TestContext.UseFilesystem()
   150  	wd, _ := filepath.EvalSymlinks(os.TempDir())
   151  
   152  	step := TestOrderedStep{
   153  		TestStep: TestStep{
   154  			Command:          "pwd",
   155  			Arguments:        []string{},
   156  			WorkingDirectory: wd,
   157  		},
   158  		SuffixArguments: []string{},
   159  	}
   160  
   161  	if runtime.GOOS == "windows" {
   162  		step.TestStep.Command = "cmd.exe"
   163  		step.Arguments = []string{"/c", "cd"}
   164  	}
   165  
   166  	_, err := ExecuteStep(ctx, c.RuntimeConfig, step)
   167  	assert.Equal(t, wd, strings.TrimRight(c.TestContext.GetOutput(), "\r\n"))
   168  	require.NoError(t, err, "Execute Step failed")
   169  }
   170  
   171  func (s TestOrderedStep) GetEnvironmentVars() map[string]string {
   172  	return s.EnvironmentVars
   173  }
   174  
   175  func TestExecuteStep_WithEnvironmentVars(t *testing.T) {
   176  	if runtime.GOOS == "windows" {
   177  		t.Skip()
   178  	}
   179  
   180  	ctx := context.Background()
   181  	c := porterruntime.NewTestRuntimeConfig(t)
   182  	c.TestContext.UseFilesystem()
   183  	step := TestOrderedStep{
   184  		TestStep: TestStep{
   185  			Command:         "env",
   186  			EnvironmentVars: map[string]string{"SOME_VAR_123": "foo"},
   187  		},
   188  	}
   189  
   190  	c.Setenv(test.ExpectedCommandEnv, "env")
   191  
   192  	_, err := ExecuteStep(ctx, c.RuntimeConfig, step)
   193  	require.NoError(t, err, "Execute Step failed")
   194  	containsEnv := strings.Contains(c.TestContext.GetOutput(), "SOME_VAR_123=foo")
   195  	// use assert.True rather than assert.Contains so that the env vars are not all sent to the test output. There might
   196  	// be sensitive stuff in there.
   197  	assert.True(t, containsEnv, "Env did not contain the key/value we expected.")
   198  }