Skip to content

Commit 9cb3ba0

Browse files
committed
chore: tests simplification
1 parent 55e9c57 commit 9cb3ba0

6 files changed

Lines changed: 87 additions & 32 deletions

File tree

internal/shell/run_cmd.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,8 @@ func (o *ShellOptions) WithForwardTFStdout(f bool) *ShellOptions {
154154
return o
155155
}
156156

157-
// WithExec sets the vexec.Exec used by RunCommandWithOutput.
157+
// WithExec installs a vexec.Exec backend that replaces the default os/exec
158+
// path used by RunCommandWithOutput. Pass nil to clear it. Intended for tests.
158159
func (o *ShellOptions) WithExec(e vexec.Exec) *ShellOptions {
159160
o.Exec = e
160161

internal/shell/run_cmd_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,35 @@ package shell_test
22

33
import (
44
"bytes"
5+
"context"
56
"testing"
67

78
"github.com/gruntwork-io/terragrunt/internal/cache"
89
"github.com/gruntwork-io/terragrunt/internal/configbridge"
910
"github.com/gruntwork-io/terragrunt/internal/iacargs"
1011
"github.com/gruntwork-io/terragrunt/internal/shell"
12+
"github.com/gruntwork-io/terragrunt/internal/vexec"
1113
"github.com/gruntwork-io/terragrunt/test/helpers/logger"
1214
"github.com/stretchr/testify/assert"
1315
"github.com/stretchr/testify/require"
1416

1517
"github.com/gruntwork-io/terragrunt/pkg/options"
1618
)
1719

20+
func TestShellOptionsWithExecRoundTrip(t *testing.T) {
21+
t.Parallel()
22+
23+
memExec := vexec.NewMemExec(func(_ context.Context, _ vexec.Invocation) vexec.Result {
24+
return vexec.Result{}
25+
})
26+
27+
opts := shell.NewShellOptions().WithExec(memExec)
28+
assert.Same(t, memExec, opts.Exec, "WithExec must store the executor")
29+
30+
opts.WithExec(nil)
31+
assert.Nil(t, opts.Exec, "WithExec(nil) must clear the executor")
32+
}
33+
1834
func TestRunShellCommand(t *testing.T) {
1935
t.Parallel()
2036

internal/util/collections_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,3 +241,41 @@ func TestMergeSlices(t *testing.T) {
241241
})
242242
}
243243
}
244+
245+
// TestEnvSliceFromMap pins the contract: length matches input, output is sorted,
246+
// and every k=v pair from the input is present (including values containing '=').
247+
func TestEnvSliceFromMap(t *testing.T) {
248+
t.Parallel()
249+
250+
testCases := []struct {
251+
in map[string]string
252+
name string
253+
want []string
254+
}{
255+
{name: "nil", in: nil, want: []string{}},
256+
{name: "empty", in: map[string]string{}, want: []string{}},
257+
{name: "single", in: map[string]string{"FOO": "bar"}, want: []string{"FOO=bar"}},
258+
{
259+
name: "multiple-overlapping-prefixes",
260+
in: map[string]string{"FOOBAR": "1", "FOO": "0", "FOOZ": "2"},
261+
want: []string{"FOO=0", "FOOBAR=1", "FOOZ=2"},
262+
},
263+
{
264+
name: "value-contains-equals",
265+
in: map[string]string{"PATH": "/a=b", "X": "1"},
266+
want: []string{"PATH=/a=b", "X=1"},
267+
},
268+
}
269+
270+
for _, tc := range testCases {
271+
t.Run(tc.name, func(t *testing.T) {
272+
t.Parallel()
273+
274+
got := util.EnvSliceFromMap(tc.in)
275+
276+
assert.Len(t, got, len(tc.in), "length must match input map")
277+
assert.True(t, slices.IsSorted(got), "output must be sorted; got %v", got)
278+
assert.ElementsMatch(t, tc.want, got, "multiset of k=v entries must match input")
279+
})
280+
}
281+
}

pkg/config/config_helpers_test.go

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,32 @@ import (
3030
)
3131

3232
// assertErrorType checks that the error chain contains an error of the same type as expectedErr.
33+
// Used by table-driven tests where the expected type is known only at runtime;
34+
// for static call sites prefer the type-parameterized requireErrorAs helper.
3335
func assertErrorType(t *testing.T, expectedErr, actualErr error) bool {
3436
t.Helper()
3537

36-
expectedType := reflect.TypeOf(expectedErr)
37-
38-
for err := actualErr; err != nil; err = errors.Unwrap(err) {
39-
if reflect.TypeOf(err) == expectedType {
40-
return true
41-
}
38+
target := reflect.New(reflect.TypeOf(expectedErr)).Interface()
39+
if errors.As(actualErr, target) {
40+
return true
4241
}
4342

4443
return assert.Fail(t, "error type mismatch", "expected error of type %T in chain, but got %T", expectedErr, actualErr)
4544
}
4645

46+
// requireErrorAs unwraps err looking for a value of type T and fails the test when none is found.
47+
// Type-parameterized wrapper over errors.As; prefer over assertErrorType when the target type is
48+
// known statically.
49+
func requireErrorAs[T error](t *testing.T, err error) T {
50+
t.Helper()
51+
52+
var target T
53+
54+
require.ErrorAs(t, err, &target)
55+
56+
return target
57+
}
58+
4759
func TestPathRelativeToInclude(t *testing.T) {
4860
t.Parallel()
4961

@@ -1424,8 +1436,7 @@ func TestStartsWithArityRegression(t *testing.T) {
14241436
require.NotPanics(t, func() {
14251437
_, err := config.StartsWith(ctx, pctx, tc.args)
14261438
require.Error(t, err, "must return error for wrong arity (%d args)", len(tc.args))
1427-
require.True(t, assertErrorType(t, config.WrongNumberOfParamsError{}, err),
1428-
"expected WrongNumberOfParamsError, got %T: %v", err, err)
1439+
requireErrorAs[config.WrongNumberOfParamsError](t, err)
14291440
}, "startswith with %d args must not panic", len(tc.args))
14301441
})
14311442
}
@@ -1453,8 +1464,7 @@ func TestEndsWithArityRegression(t *testing.T) {
14531464
require.NotPanics(t, func() {
14541465
_, err := config.EndsWith(ctx, pctx, tc.args)
14551466
require.Error(t, err, "must return error for wrong arity (%d args)", len(tc.args))
1456-
require.True(t, assertErrorType(t, config.WrongNumberOfParamsError{}, err),
1457-
"expected WrongNumberOfParamsError, got %T: %v", err, err)
1467+
requireErrorAs[config.WrongNumberOfParamsError](t, err)
14581468
}, "endswith with %d args must not panic", len(tc.args))
14591469
})
14601470
}
@@ -1482,8 +1492,7 @@ func TestStrContainsArityRegression(t *testing.T) {
14821492
require.NotPanics(t, func() {
14831493
_, err := config.StrContains(ctx, pctx, tc.args)
14841494
require.Error(t, err, "must return error for wrong arity (%d args)", len(tc.args))
1485-
require.True(t, assertErrorType(t, config.WrongNumberOfParamsError{}, err),
1486-
"expected WrongNumberOfParamsError, got %T: %v", err, err)
1495+
requireErrorAs[config.WrongNumberOfParamsError](t, err)
14871496
}, "strcontains with %d args must not panic", len(tc.args))
14881497
})
14891498
}
@@ -1515,8 +1524,7 @@ func TestRunCommandOptionsOnlyArityRegression(t *testing.T) {
15151524
require.NotPanics(t, func() {
15161525
_, err := config.RunCommand(ctx, pctx, l, tc.params)
15171526
require.Error(t, err, "must return error when only option flags are supplied (%v)", tc.params)
1518-
require.True(t, assertErrorType(t, config.EmptyStringNotAllowedError(""), err),
1519-
"expected EmptyStringNotAllowedError, got %T: %v", err, err)
1527+
requireErrorAs[config.EmptyStringNotAllowedError](t, err)
15201528
}, "run_cmd with options-only %v must not panic", tc.params)
15211529
})
15221530
}

pkg/config/dependency.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ import (
4141
"github.com/gruntwork-io/terragrunt/internal/shell"
4242
"github.com/gruntwork-io/terragrunt/internal/tf"
4343
"github.com/gruntwork-io/terragrunt/internal/util"
44-
"github.com/gruntwork-io/terragrunt/internal/vexec"
4544
"github.com/gruntwork-io/terragrunt/pkg/config/hclparse"
4645
)
4746

@@ -1450,11 +1449,6 @@ func runTerragruntOutputJSON(ctx context.Context, pctx *ParsingContext, l log.Lo
14501449

14511450
// shellRunOptsFromPctx builds a *shell.ShellOptions from ParsingContext flat fields.
14521451
func shellRunOptsFromPctx(pctx *ParsingContext) *shell.ShellOptions {
1453-
exec := pctx.Exec
1454-
if exec == nil {
1455-
exec = vexec.NewOSExec()
1456-
}
1457-
14581452
return shell.NewShellOptions().
14591453
WithWorkingDir(pctx.WorkingDir).
14601454
WithEnv(pctx.Env).
@@ -1466,7 +1460,7 @@ func shellRunOptsFromPctx(pctx *ParsingContext) *shell.ShellOptions {
14661460
WithExperiments(pctx.Experiments).
14671461
WithHeadless(pctx.Headless).
14681462
WithForwardTFStdout(pctx.ForwardTFStdout).
1469-
WithExec(exec)
1463+
WithExec(pctx.Exec)
14701464
}
14711465

14721466
// tfRunOptsFromPctx builds a *tf.RunOptions from ParsingContext flat fields.

pkg/config/fuzz_test.go

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,11 @@ func FuzzHCLStringHelpers(f *testing.F) {
4343

4444
if len(args) != 2 {
4545
require.Error(t, swErr, "startswith with %d args must error", len(args))
46-
require.True(t, assertErrorType(t, config.WrongNumberOfParamsError{}, swErr),
47-
"startswith expected WrongNumberOfParamsError, got %T: %v", swErr, swErr)
46+
requireErrorAs[config.WrongNumberOfParamsError](t, swErr)
4847
require.Error(t, ewErr, "endswith with %d args must error", len(args))
49-
require.True(t, assertErrorType(t, config.WrongNumberOfParamsError{}, ewErr),
50-
"endswith expected WrongNumberOfParamsError, got %T: %v", ewErr, ewErr)
48+
requireErrorAs[config.WrongNumberOfParamsError](t, ewErr)
5149
require.Error(t, scErr, "strcontains with %d args must error", len(args))
52-
require.True(t, assertErrorType(t, config.WrongNumberOfParamsError{}, scErr),
53-
"strcontains expected WrongNumberOfParamsError, got %T: %v", scErr, scErr)
50+
requireErrorAs[config.WrongNumberOfParamsError](t, scErr)
5451

5552
return
5653
}
@@ -101,6 +98,9 @@ func FuzzHCLRunCommand(f *testing.F) {
10198
"--unknown-flag\x00args",
10299
"\x00",
103100
"\x00\x00",
101+
"--terragrunt-quiet\x00\x00/bin/echo\x00hi", // empty arg between flags and command
102+
"/bin/echo\x00--terragrunt-quiet", // trailing flag (not stripped — only leading flags are)
103+
" \x00--terragrunt-quiet\x00cmd", // whitespace as command
104104
}
105105
for _, s := range seeds {
106106
f.Add(s)
@@ -140,15 +140,13 @@ func FuzzHCLRunCommand(f *testing.F) {
140140
switch {
141141
case conflict:
142142
require.Error(t, err, "expected ConflictingRunCmdCacheOptionsError for %q", raw)
143-
require.True(t, assertErrorType(t, config.ConflictingRunCmdCacheOptionsError{}, err),
144-
"expected ConflictingRunCmdCacheOptionsError, got %T: %v", err, err)
143+
requireErrorAs[config.ConflictingRunCmdCacheOptionsError](t, err)
145144
require.Empty(t, out)
146145
require.Equal(t, int32(0), calls.Load(),
147146
"exec must not run on the conflict path (got %d calls)", calls.Load())
148147
case len(stripped) == 0:
149148
require.Error(t, err, "expected EmptyStringNotAllowedError for %q", raw)
150-
require.True(t, assertErrorType(t, config.EmptyStringNotAllowedError(""), err),
151-
"expected EmptyStringNotAllowedError, got %T: %v", err, err)
149+
requireErrorAs[config.EmptyStringNotAllowedError](t, err)
152150
require.Empty(t, out)
153151
require.Equal(t, int32(0), calls.Load(),
154152
"exec must not run on the empty-args path (got %d calls)", calls.Load())

0 commit comments

Comments
 (0)