summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--config.go2
-rw-r--r--docs/content/config.md10
-rw-r--r--docs/content/usage.md10
-rw-r--r--internal/cmd/config.go4
-rw-r--r--internal/cmd/config_test.go15
-rw-r--r--internal/config/configfile.go5
-rw-r--r--internal/iohelpers/writers.go10
-rw-r--r--internal/tests/integration/basic_test.go3
-rw-r--r--internal/tests/integration/gomplateignore_test.go136
-rw-r--r--render.go4
-rw-r--r--template.go37
-rw-r--r--template_test.go35
12 files changed, 119 insertions, 152 deletions
diff --git a/config.go b/config.go
index 802afd74..64b49dba 100644
--- a/config.go
+++ b/config.go
@@ -136,7 +136,7 @@ func (o *Config) toNewConfig() (*config.Config, error) {
LDelim: o.LDelim,
RDelim: o.RDelim,
Stdin: os.Stdin,
- Stdout: &iohelpers.NopCloser{Writer: o.Out},
+ Stdout: iohelpers.NopCloser(o.Out),
Stderr: os.Stderr,
}
err := cfg.ParsePluginFlags(o.Plugins)
diff --git a/docs/content/config.md b/docs/content/config.md
index a260c2d2..a0eaf485 100644
--- a/docs/content/config.md
+++ b/docs/content/config.md
@@ -447,16 +447,6 @@ Overrides the right template delimiter.
rightDelim: '))'
```
-## `suppressEmpty`
-
-See _[Suppressing empty output](../usage/#suppressing-empty-output)_
-
-Suppresses empty output (i.e. output consisting of only whitespace). Can also be set with the `GOMPLATE_SUPPRESS_EMPTY` environment variable.
-
-```yaml
-suppressEmpty: true
-```
-
## `templates`
See [`--template`/`-t`](../usage/#template-t).
diff --git a/docs/content/usage.md b/docs/content/usage.md
index 1ac2ca96..3d390978 100644
--- a/docs/content/usage.md
+++ b/docs/content/usage.md
@@ -424,16 +424,10 @@ hello world
See also [`--exec-pipe`](#exec-pipe) for piping output directly into the
post-exec command.
-## Suppressing empty output
+## Empty output
-Sometimes it can be desirable to suppress empty output (i.e. output consisting of only whitespace). To do so, set `suppressEmpty: true` in your [config](../config/#suppressempty) file, or `GOMPLATE_SUPPRESS_EMPTY=true` in your environment:
+If the template renders to an empty file (i.e. output consisting of only whitespace), gomplate will not write the output.
-```console
-$ export GOMPLATE_SUPPRESS_EMPTY=true
-$ gomplate -i '{{ print " \n" }}' -o out
-$ cat out
-cat: out: No such file or directory
-```
[default context]: ../syntax/#the-context
[context]: ../syntax/#the-context
diff --git a/internal/cmd/config.go b/internal/cmd/config.go
index 62777061..fc64f8e4 100644
--- a/internal/cmd/config.go
+++ b/internal/cmd/config.go
@@ -250,10 +250,6 @@ func applyEnvVars(_ context.Context, cfg *config.Config) (*config.Config, error)
cfg.PluginTimeout = t
}
- if !cfg.SuppressEmpty && conv.ToBool(env.Getenv("GOMPLATE_SUPPRESS_EMPTY", "false")) {
- cfg.SuppressEmpty = true
- }
-
if !cfg.Experimental && conv.ToBool(env.Getenv("GOMPLATE_EXPERIMENTAL", "false")) {
cfg.Experimental = true
}
diff --git a/internal/cmd/config_test.go b/internal/cmd/config_test.go
index a3ff4935..1c9adfe5 100644
--- a/internal/cmd/config_test.go
+++ b/internal/cmd/config_test.go
@@ -221,21 +221,6 @@ func TestApplyEnvVars(t *testing.T) {
},
{
&config.Config{},
- &config.Config{SuppressEmpty: false},
- "GOMPLATE_SUPPRESS_EMPTY", "bogus",
- },
- {
- &config.Config{},
- &config.Config{SuppressEmpty: true},
- "GOMPLATE_SUPPRESS_EMPTY", "true",
- },
- {
- &config.Config{SuppressEmpty: true},
- &config.Config{SuppressEmpty: true},
- "GOMPLATE_SUPPRESS_EMPTY", "false",
- },
- {
- &config.Config{},
&config.Config{Experimental: false},
"GOMPLATE_EXPERIMENTAL", "bogus",
},
diff --git a/internal/config/configfile.go b/internal/config/configfile.go
index 76fc5306..d255a36c 100644
--- a/internal/config/configfile.go
+++ b/internal/config/configfile.go
@@ -69,9 +69,8 @@ type Config struct {
PluginTimeout time.Duration `yaml:"pluginTimeout,omitempty"`
- ExecPipe bool `yaml:"execPipe,omitempty"`
- SuppressEmpty bool `yaml:"suppressEmpty,omitempty"`
- Experimental bool `yaml:"experimental,omitempty"`
+ ExecPipe bool `yaml:"execPipe,omitempty"`
+ Experimental bool `yaml:"experimental,omitempty"`
}
type experimentalCtxKey struct{}
diff --git a/internal/iohelpers/writers.go b/internal/iohelpers/writers.go
index 870fa4cb..543c050b 100644
--- a/internal/iohelpers/writers.go
+++ b/internal/iohelpers/writers.go
@@ -82,17 +82,21 @@ func allWhitespace(p []byte) bool {
// NopCloser returns a WriteCloser with a no-op Close method wrapping
// the provided io.Writer.
-type NopCloser struct {
+func NopCloser(w io.Writer) io.WriteCloser {
+ return &nopCloser{Writer: w}
+}
+
+type nopCloser struct {
io.Writer
}
// Close - implements io.Closer
-func (n *NopCloser) Close() error {
+func (n *nopCloser) Close() error {
return nil
}
var (
- _ io.WriteCloser = (*NopCloser)(nil)
+ _ io.WriteCloser = (*nopCloser)(nil)
_ io.WriteCloser = (*emptySkipper)(nil)
_ io.WriteCloser = (*sameSkipper)(nil)
)
diff --git a/internal/tests/integration/basic_test.go b/internal/tests/integration/basic_test.go
index 1e61646f..d2c4fa35 100644
--- a/internal/tests/integration/basic_test.go
+++ b/internal/tests/integration/basic_test.go
@@ -178,8 +178,7 @@ func TestBasic_EmptyOutputSuppression(t *testing.T) {
tmpDir := setupBasicTest(t)
out := tmpDir.Join("out")
o, e, err := cmd(t, "-i", `{{print "\t \n\n\r\n\t\t \v\n"}}`,
- "-o", out).
- withEnv("GOMPLATE_SUPPRESS_EMPTY", "true").run()
+ "-o", out).run()
assertSuccess(t, o, e, err, "")
_, err = os.Stat(out)
diff --git a/internal/tests/integration/gomplateignore_test.go b/internal/tests/integration/gomplateignore_test.go
index a49d41ed..6679a8b4 100644
--- a/internal/tests/integration/gomplateignore_test.go
+++ b/internal/tests/integration/gomplateignore_test.go
@@ -70,8 +70,8 @@ func TestGomplateignore_Simple(t *testing.T) {
files, err := execute(t, `# all dot files
.*
*.log`,
- tfs.WithFile("empty.log", ""),
- tfs.WithFile("rain.txt", ""))
+ tfs.WithFile("foo.log", "..."),
+ tfs.WithFile("rain.txt", "..."))
require.NoError(t, err)
assert.Equal(t, []string{"rain.txt"}, files)
@@ -91,12 +91,12 @@ f[o]o/bar
tfs.WithDir("foo",
tfs.WithDir("bar",
tfs.WithDir("tool",
- tfs.WithFile("lex.txt", ""),
+ tfs.WithFile("lex.txt", "..."),
),
- tfs.WithFile("1.txt", ""),
+ tfs.WithFile("1.txt", "..."),
),
tfs.WithDir("tar",
- tfs.WithFile("2.txt", ""),
+ tfs.WithFile("2.txt", "..."),
),
),
)
@@ -110,10 +110,10 @@ func TestGomplateignore_Root(t *testing.T) {
files, err := execute(t, `.gomplateignore
/1.txt`,
tfs.WithDir("sub",
- tfs.WithFile("1.txt", ""),
- tfs.WithFile("2.txt", ""),
+ tfs.WithFile("1.txt", "..."),
+ tfs.WithFile("2.txt", "..."),
),
- tfs.WithFile("1.txt", ""),
+ tfs.WithFile("1.txt", "..."),
)
require.NoError(t, err)
@@ -127,14 +127,14 @@ func TestGomplateignore_Exclusion(t *testing.T) {
!/e2.txt
en/e3.txt
!`,
- tfs.WithFile("!", ""),
- tfs.WithFile("e1.txt", ""),
- tfs.WithFile("e2.txt", ""),
- tfs.WithFile("e3.txt", ""),
+ tfs.WithFile("!", "xxx"),
+ tfs.WithFile("e1.txt", "xxx"),
+ tfs.WithFile("e2.txt", "xxx"),
+ tfs.WithFile("e3.txt", "xxx"),
tfs.WithDir("en",
- tfs.WithFile("e1.txt", ""),
- tfs.WithFile("e2.txt", ""),
- tfs.WithFile("e3.txt", ""),
+ tfs.WithFile("e1.txt", "xxx"),
+ tfs.WithFile("e2.txt", "xxx"),
+ tfs.WithFile("e3.txt", "xxx"),
),
)
@@ -148,13 +148,13 @@ func TestGomplateignore_Nested(t *testing.T) {
tfs.WithDir("inner",
tfs.WithDir("inner2",
tfs.WithFile(".gomplateignore", "moss.ini\n!/jess.ini"),
- tfs.WithFile("jess.ini", ""),
- tfs.WithFile("moss.ini", "")),
+ tfs.WithFile("jess.ini", "xxx"),
+ tfs.WithFile("moss.ini", "xxx")),
tfs.WithFile(".gomplateignore", "*.lst\njess.ini"),
- tfs.WithFile("2.lst", ""),
- tfs.WithFile("foo.md", ""),
+ tfs.WithFile("2.lst", "xxx"),
+ tfs.WithFile("foo.md", "xxx"),
),
- tfs.WithFile("1.txt", ""),
+ tfs.WithFile("1.txt", "xxx"),
)
require.NoError(t, err)
@@ -170,17 +170,17 @@ world.txt`,
tfs.WithDir("aa",
tfs.WithDir("a1",
tfs.WithDir("a2",
- tfs.WithFile("hello.txt", ""),
- tfs.WithFile("world.txt", "")),
- tfs.WithFile("hello.txt", ""),
- tfs.WithFile("world.txt", "")),
- tfs.WithFile("hello.txt", ""),
- tfs.WithFile("world.txt", "")),
+ tfs.WithFile("hello.txt", "..."),
+ tfs.WithFile("world.txt", "...")),
+ tfs.WithFile("hello.txt", "..."),
+ tfs.WithFile("world.txt", "...")),
+ tfs.WithFile("hello.txt", "..."),
+ tfs.WithFile("world.txt", "...")),
tfs.WithDir("bb",
- tfs.WithFile("hello.txt", ""),
- tfs.WithFile("world.txt", "")),
- tfs.WithFile("hello.txt", ""),
- tfs.WithFile("world.txt", ""),
+ tfs.WithFile("hello.txt", "..."),
+ tfs.WithFile("world.txt", "...")),
+ tfs.WithFile("hello.txt", "..."),
+ tfs.WithFile("world.txt", "..."),
)
require.NoError(t, err)
@@ -195,11 +195,11 @@ loss.txt
!2.log
`,
tfs.WithDir("loss.txt",
- tfs.WithFile("1.log", ""),
- tfs.WithFile("2.log", "")),
+ tfs.WithFile("1.log", "xxx"),
+ tfs.WithFile("2.log", "xxx")),
tfs.WithDir("foo",
- tfs.WithFile("loss.txt", ""),
- tfs.WithFile("bare.txt", "")),
+ tfs.WithFile("loss.txt", "xxx"),
+ tfs.WithFile("bare.txt", "xxx")),
)
require.NoError(t, err)
@@ -215,11 +215,11 @@ func TestGomplateignore_LeadingSpace(t *testing.T) {
! dart.log
`,
tfs.WithDir("inner",
- tfs.WithFile(" what.txt", ""),
- tfs.WithFile(" dart.log", "")),
+ tfs.WithFile(" what.txt", "xxx"),
+ tfs.WithFile(" dart.log", "xxx")),
tfs.WithDir("inner2",
- tfs.WithFile(" what.txt", "")),
- tfs.WithFile(" what.txt", ""),
+ tfs.WithFile(" what.txt", "xxx")),
+ tfs.WithFile(" what.txt", "xxx"),
)
require.NoError(t, err)
@@ -236,19 +236,19 @@ func TestGomplateignore_WithExcludes(t *testing.T) {
"--exclude", "sprites/*.ini",
},
tfs.WithDir("logs",
- tfs.WithFile("archive.zip", ""),
- tfs.WithFile("engine.log", ""),
- tfs.WithFile("skills.log", "")),
+ tfs.WithFile("archive.zip", "x"),
+ tfs.WithFile("engine.log", "x"),
+ tfs.WithFile("skills.log", "x")),
tfs.WithDir("rules",
- tfs.WithFile("index.csv", ""),
- tfs.WithFile("fire.txt", ""),
- tfs.WithFile("earth.txt", "")),
+ tfs.WithFile("index.csv", "x"),
+ tfs.WithFile("fire.txt", "x"),
+ tfs.WithFile("earth.txt", "x")),
tfs.WithDir("sprites",
- tfs.WithFile("human.csv", ""),
- tfs.WithFile("demon.xml", ""),
- tfs.WithFile("alien.ini", "")),
- tfs.WithFile("manifest.json", ""),
- tfs.WithFile("crash.bin", ""),
+ tfs.WithFile("human.csv", "x"),
+ tfs.WithFile("demon.xml", "x"),
+ tfs.WithFile("alien.ini", "x")),
+ tfs.WithFile("manifest.json", "x"),
+ tfs.WithFile("crash.bin", "x"),
)
require.NoError(t, err)
@@ -265,15 +265,15 @@ func TestGomplateignore_WithIncludes(t *testing.T) {
"--exclude", "rules/*.txt",
},
tfs.WithDir("logs",
- tfs.WithFile("archive.zip", ""),
- tfs.WithFile("engine.log", ""),
- tfs.WithFile("skills.log", "")),
+ tfs.WithFile("archive.zip", "x"),
+ tfs.WithFile("engine.log", "x"),
+ tfs.WithFile("skills.log", "x")),
tfs.WithDir("rules",
- tfs.WithFile("index.csv", ""),
- tfs.WithFile("fire.txt", ""),
- tfs.WithFile("earth.txt", "")),
- tfs.WithFile("manifest.json", ""),
- tfs.WithFile("crash.bin", ""),
+ tfs.WithFile("index.csv", "x"),
+ tfs.WithFile("fire.txt", "x"),
+ tfs.WithFile("earth.txt", "x")),
+ tfs.WithFile("manifest.json", "x"),
+ tfs.WithFile("crash.bin", "x"),
)
require.NoError(t, err)
@@ -290,19 +290,19 @@ func TestGomplateignore_WithExcludeProcessing(t *testing.T) {
"--exclude", "sprites/*.ini",
},
tfs.WithDir("logs",
- tfs.WithFile("archive.zip", ""),
- tfs.WithFile("engine.log", ""),
- tfs.WithFile("skills.log", "")),
+ tfs.WithFile("archive.zip", "xxx"),
+ tfs.WithFile("engine.log", "xxx"),
+ tfs.WithFile("skills.log", "xxx")),
tfs.WithDir("rules",
- tfs.WithFile("index.csv", ""),
- tfs.WithFile("fire.txt", ""),
- tfs.WithFile("earth.txt", "")),
+ tfs.WithFile("index.csv", "xxx"),
+ tfs.WithFile("fire.txt", "xxx"),
+ tfs.WithFile("earth.txt", "xxx")),
tfs.WithDir("sprites",
- tfs.WithFile("human.csv", ""),
- tfs.WithFile("demon.xml", ""),
- tfs.WithFile("alien.ini", "")),
- tfs.WithFile("manifest.json", ""),
- tfs.WithFile("crash.bin", ""),
+ tfs.WithFile("human.csv", "xxx"),
+ tfs.WithFile("demon.xml", "xxx"),
+ tfs.WithFile("alien.ini", "xxx")),
+ tfs.WithFile("manifest.json", "xxx"),
+ tfs.WithFile("crash.bin", "xxx"),
)
require.NoError(t, err)
diff --git a/render.go b/render.go
index 7b377291..1978f4fe 100644
--- a/render.go
+++ b/render.go
@@ -6,7 +6,6 @@ import (
"io"
"net/http"
"net/url"
- "os"
"sync"
"text/template"
"time"
@@ -244,8 +243,7 @@ func (t *Renderer) renderTemplatesWithData(ctx context.Context, templates []Temp
func (t *Renderer) renderTemplate(ctx context.Context, template Template, f template.FuncMap, tmplctx interface{}) error {
if template.Writer != nil {
- wr, ok := template.Writer.(io.Closer)
- if ok && wr != os.Stdout {
+ if wr, ok := template.Writer.(io.Closer); ok {
defer wr.Close()
}
}
diff --git a/template.go b/template.go
index dbdabf36..812aafe4 100644
--- a/template.go
+++ b/template.go
@@ -192,7 +192,7 @@ func gatherTemplates(ctx context.Context, cfg *config.Config, outFileNamer func(
case cfg.Input != "":
// open the output file - no need to close it, as it will be closed by the
// caller later
- target, oerr := openOutFile(ctx, cfg.OutputFiles[0], 0o755, mode, modeOverride, cfg.Stdout, cfg.SuppressEmpty)
+ target, oerr := openOutFile(ctx, cfg.OutputFiles[0], 0o755, mode, modeOverride, cfg.Stdout)
if oerr != nil {
return nil, fmt.Errorf("openOutFile: %w", oerr)
}
@@ -367,7 +367,7 @@ func readInFile(ctx context.Context, cfg *config.Config, inFile string, mode os.
func getOutfileHandler(ctx context.Context, cfg *config.Config, outFile string, mode os.FileMode, modeOverride bool) (io.Writer, error) {
// open the output file - no need to close it, as it will be closed by the
// caller later
- target, err := openOutFile(ctx, outFile, 0o755, mode, modeOverride, cfg.Stdout, cfg.SuppressEmpty)
+ target, err := openOutFile(ctx, outFile, 0o755, mode, modeOverride, cfg.Stdout)
if err != nil {
return nil, fmt.Errorf("openOutFile: %w", err)
}
@@ -417,29 +417,20 @@ func fileToTemplate(ctx context.Context, cfg *config.Config, inFile, outFile str
// openOutFile returns a writer for the given file, creating the file if it
// doesn't exist yet, and creating the parent directories if necessary. Will
-// defer actual opening until the first write (or the first non-empty write if
-// 'suppressEmpty' is true). If the file already exists, it will not be
-// overwritten until the first difference is encountered.
+// defer actual opening until the first non-empty write. If the file already
+// exists, it will not be overwritten until the first difference is encountered.
//
-// TODO: the 'suppressEmpty' behaviour should be always enabled, in the next
-// major release (v4.x).
+// TODO: dirMode is always called with 0o755 - should either remove or make it configurable
//
-//nolint:unparam // TODO: dirMode is always called with 0o755 - should either remove or make it configurable
-func openOutFile(ctx context.Context, filename string, dirMode, mode os.FileMode, modeOverride bool, stdout io.Writer, suppressEmpty bool) (out io.Writer, err error) {
- if suppressEmpty {
- out = iohelpers.NewEmptySkipper(func() (io.Writer, error) {
- if filename == "-" {
- return stdout, nil
- }
- return createOutFile(ctx, filename, dirMode, mode, modeOverride)
- })
- return out, nil
- }
-
- if filename == "-" {
- return stdout, nil
- }
- return createOutFile(ctx, filename, dirMode, mode, modeOverride)
+//nolint:unparam
+func openOutFile(ctx context.Context, filename string, dirMode, mode os.FileMode, modeOverride bool, stdout io.Writer) (out io.Writer, err error) {
+ out = iohelpers.NewEmptySkipper(func() (io.Writer, error) {
+ if filename == "-" {
+ return iohelpers.NopCloser(stdout), nil
+ }
+ return createOutFile(ctx, filename, dirMode, mode, modeOverride)
+ })
+ return out, nil
}
func createOutFile(ctx context.Context, filename string, dirMode, mode os.FileMode, modeOverride bool) (out io.WriteCloser, err error) {
diff --git a/template_test.go b/template_test.go
index 1ac5150f..60607b2f 100644
--- a/template_test.go
+++ b/template_test.go
@@ -29,8 +29,10 @@ func TestOpenOutFile(t *testing.T) {
ctx := datafs.ContextWithFSProvider(context.Background(), datafs.WrappedFSProvider(fsys, "file"))
- cfg := &config.Config{Stdout: &bytes.Buffer{}}
- f, err := openOutFile(ctx, "/tmp/foo", 0o755, 0o644, false, nil, false)
+ f, err := openOutFile(ctx, "/tmp/foo", 0o755, 0o644, false, nil)
+ require.NoError(t, err)
+
+ _, err = f.Write([]byte("hello world"))
require.NoError(t, err)
wc, ok := f.(io.WriteCloser)
@@ -44,9 +46,12 @@ func TestOpenOutFile(t *testing.T) {
out := &bytes.Buffer{}
- f, err = openOutFile(ctx, "-", 0o755, 0o644, false, out, false)
+ f, err = openOutFile(ctx, "-", 0o755, 0o644, false, out)
require.NoError(t, err)
- assert.Equal(t, cfg.Stdout, f)
+
+ _, err = f.Write([]byte("hello world"))
+ require.NoError(t, err)
+ assert.Equal(t, "hello world", out.String())
}
func TestGatherTemplates(t *testing.T) {
@@ -76,16 +81,20 @@ func TestGatherTemplates(t *testing.T) {
require.NoError(t, err)
assert.Len(t, templates, 1)
+ buf := &bytes.Buffer{}
cfg = &config.Config{
Input: "foo",
- Stdout: &bytes.Buffer{},
+ Stdout: buf,
}
cfg.ApplyDefaults()
templates, err = gatherTemplates(ctx, cfg, nil)
require.NoError(t, err)
assert.Len(t, templates, 1)
assert.Equal(t, "foo", templates[0].Text)
- assert.Equal(t, cfg.Stdout, templates[0].Writer)
+
+ _, err = templates[0].Writer.Write([]byte("hello world"))
+ require.NoError(t, err)
+ assert.Equal(t, "hello world", buf.String())
templates, err = gatherTemplates(ctx, &config.Config{
Input: "foo",
@@ -106,41 +115,43 @@ func TestGatherTemplates(t *testing.T) {
assert.Equal(t, iohelpers.NormalizeFileMode(0o644), info.Mode())
_ = hackpadfs.Remove(fsys, "out")
+ buf = &bytes.Buffer{}
cfg = &config.Config{
InputFiles: []string{"foo"},
OutputFiles: []string{"out"},
- Stdout: &bytes.Buffer{},
+ Stdout: buf,
}
templates, err = gatherTemplates(ctx, cfg, nil)
require.NoError(t, err)
assert.Len(t, templates, 1)
assert.Equal(t, "bar", templates[0].Text)
- assert.NotEqual(t, cfg.Stdout, templates[0].Writer)
- // assert.Equal(t, os.FileMode(0o600), templates[0].mode)
_, err = templates[0].Writer.Write([]byte("hello world"))
require.NoError(t, err)
+ // negative test - we should not be writing to stdout
+ assert.NotEqual(t, "hello world", buf.String())
info, err = hackpadfs.Stat(fsys, "out")
require.NoError(t, err)
assert.Equal(t, iohelpers.NormalizeFileMode(0o600), info.Mode())
hackpadfs.Remove(fsys, "out")
+ buf = &bytes.Buffer{}
cfg = &config.Config{
InputFiles: []string{"foo"},
OutputFiles: []string{"out"},
OutMode: "755",
- Stdout: &bytes.Buffer{},
+ Stdout: buf,
}
templates, err = gatherTemplates(ctx, cfg, nil)
require.NoError(t, err)
assert.Len(t, templates, 1)
assert.Equal(t, "bar", templates[0].Text)
- assert.NotEqual(t, cfg.Stdout, templates[0].Writer)
- // assert.Equal(t, iohelpers.NormalizeFileMode(0o755), templates[0].mode)
_, err = templates[0].Writer.Write([]byte("hello world"))
require.NoError(t, err)
+ // negative test - we should not be writing to stdout
+ assert.NotEqual(t, "hello world", buf.String())
info, err = hackpadfs.Stat(fsys, "out")
require.NoError(t, err)