-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcommand_test.go
More file actions
178 lines (161 loc) · 5.83 KB
/
Copy pathcommand_test.go
File metadata and controls
178 lines (161 loc) · 5.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
package command
import (
"context"
"slices"
"testing"
gloo "github.com/gloo-foo/framework"
"github.com/gloo-foo/framework/patterns"
"github.com/spf13/afero"
)
// captured records what a fake subprocess builder was handed, so a test can
// assert exactly the command Perl would have forked — proving the contract
// without executing perl (which would be non-hermetic and depend on a working
// perl install).
type captured struct {
name patterns.ProcessName
args []patterns.ProcessArg
}
// fakeBuilder returns a subprocessBuilder that records its inputs into got and
// yields a pass-through Command, so no real process is ever spawned. The
// pass-through lets input-wiring branches run end to end without perl.
func fakeBuilder(got *captured) subprocessBuilder {
return func(name patterns.ProcessName, args ...patterns.ProcessArg) gloo.Command[[]byte, []byte] {
got.name = name
got.args = args
return gloo.FuncCommand[[]byte, []byte](
func(_ context.Context, in gloo.Stream[[]byte]) gloo.Stream[[]byte] { return in },
)
}
}
// memFs returns an in-memory filesystem seeded with one file, so File-positional
// input is exercised without touching the real disk.
func memFs(t *testing.T, name, content string) afero.Fs {
t.Helper()
fs := afero.NewMemMapFs()
if err := afero.WriteFile(fs, name, []byte(content), 0o644); err != nil {
t.Fatalf("seeding %q: %v", name, err)
}
return fs
}
// run executes cmd against the given input lines and collects its output lines.
func run(t *testing.T, cmd gloo.Command[[]byte, []byte], input ...string) ([]string, error) {
t.Helper()
src := gloo.StreamOf(toBytes(input)...)
out := cmd.Execute(context.Background(), src)
return collect(out)
}
// toBytes converts string lines to byte lines for stream input.
func toBytes(lines []string) [][]byte {
out := make([][]byte, len(lines))
for i, l := range lines {
out[i] = []byte(l)
}
return out
}
// collect drains a byte stream into string lines, surfacing the first error.
func collect(s gloo.Stream[[]byte]) ([]string, error) {
var lines []string
for item := range s.Chan() {
if item.Error != nil {
return nil, item.Error
}
lines = append(lines, string(item.Value))
}
return lines, nil
}
func TestPerlWith_ScriptPositionalPromotedToFlag(t *testing.T) {
var got captured
perlWith(fakeBuilder(&got), afero.NewMemMapFs(), `print "hi"`)
if got.name != "perl" {
t.Errorf("forked %q, want \"perl\"", got.name)
}
if !slices.Equal(got.args, []patterns.ProcessArg{"-e", `print "hi"`}) {
t.Errorf("got args %q, want [-e print \"hi\"]", got.args)
}
}
func TestPerlWith_ExplicitScriptFlag(t *testing.T) {
var got captured
perlWith(fakeBuilder(&got), afero.NewMemMapFs(), PerlScript(`s/a/b/`))
if !slices.Equal(got.args, []patterns.ProcessArg{"-e", `s/a/b/`}) {
t.Errorf("got args %q, want [-e s/a/b/]", got.args)
}
}
func TestPerlWith_AllSwitchesInCanonicalOrder(t *testing.T) {
var got captured
perlWith(fakeBuilder(&got), afero.NewMemMapFs(),
PerlScript(`X`), PerlLoop, PerlPrint, PerlAutoSplit)
if !slices.Equal(got.args, []patterns.ProcessArg{"-n", "-p", "-a", "-e", "X"}) {
t.Errorf("got args %q, want [-n -p -a -e X]", got.args)
}
}
func TestPerlWith_DisabledSwitchesOmitted(t *testing.T) {
var got captured
perlWith(fakeBuilder(&got), afero.NewMemMapFs(),
PerlScript(`X`), PerlNoLoop, PerlNoPrint, PerlNoAutoSplit)
if !slices.Equal(got.args, []patterns.ProcessArg{"-e", "X"}) {
t.Errorf("got args %q, want [-e X]", got.args)
}
}
// With an explicit PerlScript flag present, a bare string is no longer promoted
// to the script — it is classified as a positional File path instead.
func TestPerlWith_ExplicitScriptKeepsBareStringAsFile(t *testing.T) {
var got captured
fs := memFs(t, "in.txt", "from-file\n")
cmd := perlWith(fakeBuilder(&got), fs, PerlScript(`pass`), "in.txt")
if !slices.Equal(got.args, []patterns.ProcessArg{"-e", `pass`}) {
t.Errorf("got args %q, want [-e pass]", got.args)
}
lines, err := run(t, cmd)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !slices.Equal(lines, []string{"from-file"}) {
t.Errorf("got %q, want [from-file]", lines)
}
}
// No positional input: Perl returns the bare subprocess Command, which (via the
// fake) passes the pipeline stream straight through.
func TestPerlWith_NoInputReturnsBareSubprocess(t *testing.T) {
var got captured
cmd := perlWith(fakeBuilder(&got), afero.NewMemMapFs(), PerlScript(`pass`))
lines, err := run(t, cmd, "a", "b")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !slices.Equal(lines, []string{"a", "b"}) {
t.Errorf("got %q, want [a b]", lines)
}
}
// A File positional opens the file and streams its lines to perl's stdin; the
// pass-through fake echoes them back, proving the wiring.
func TestPerlWith_FileInputStreamsToStdin(t *testing.T) {
var got captured
fs := memFs(t, "data.txt", "one\ntwo\n")
cmd := perlWith(fakeBuilder(&got), fs, PerlScript(`pass`), "data.txt")
lines, err := run(t, cmd)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !slices.Equal(lines, []string{"one", "two"}) {
t.Errorf("got %q, want [one two]", lines)
}
}
// A missing File positional surfaces an error through the stream rather than
// forking perl with unusable input.
func TestPerlWith_MissingFileInputYieldsError(t *testing.T) {
var got captured
cmd := perlWith(fakeBuilder(&got), afero.NewMemMapFs(), PerlScript(`pass`), "absent.txt")
_, err := run(t, cmd)
if err == nil {
t.Fatal("expected an error for a missing input file, got nil")
}
}
// Perl wires the production builder (patterns.Subprocess) and the OS filesystem.
// Constructing the Command must not fork perl, so this asserts only that a
// usable Command is returned; the argument and wiring contracts are proven
// against the fake builder above.
func TestPerl_ReturnsCommand(t *testing.T) {
if Perl(PerlScript(`print "x"`)) == nil {
t.Fatal("Perl returned a nil Command")
}
}