From 6799a5027ad8043fabc785e4c5e397366fa8c37d Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Tue, 13 Dec 2022 22:54:24 +0100 Subject: [PATCH] testscript: add RequireUniqueNames parameter Co-authored-by: Roger Peppe --- .../testdata/testscript_duplicate_name.txt | 12 ++++++++ testscript/testscript.go | 28 ++++++++++++++++++- testscript/testscript_test.go | 2 ++ 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 testscript/testdata/testscript_duplicate_name.txt diff --git a/testscript/testdata/testscript_duplicate_name.txt b/testscript/testdata/testscript_duplicate_name.txt new file mode 100644 index 00000000..b8fd4c6d --- /dev/null +++ b/testscript/testdata/testscript_duplicate_name.txt @@ -0,0 +1,12 @@ +# Check that RequireUniqueNames works; +# it should reject txtar archives with duplicate names as defined by the host system. + +unquote scripts-normalized/testscript.txt + +testscript scripts-normalized +! testscript -unique-names scripts-normalized +stdout '.* would overwrite .* \(because RequireUniqueNames is enabled\)' + +-- scripts-normalized/testscript.txt -- +>-- file -- +>-- dir/../file -- \ No newline at end of file diff --git a/testscript/testscript.go b/testscript/testscript.go index 8fedb521..00fcbf0f 100644 --- a/testscript/testscript.go +++ b/testscript/testscript.go @@ -14,6 +14,7 @@ import ( "flag" "fmt" "go/build" + "io/fs" "io/ioutil" "os" "os/exec" @@ -174,6 +175,10 @@ type Params struct { // executions explicit. RequireExplicitExec bool + // RequireUniqueNames requires that names in the txtar archive are unique. + // By default, later entries silently overwrite earlier ones. + RequireUniqueNames bool + // ContinueOnError causes a testscript to try to continue in // the face of errors. Once an error has occurred, the script // will continue as if in verbose mode. @@ -372,6 +377,22 @@ type backgroundCmd struct { neg bool // if true, cmd should fail } +func writeFile(name string, data []byte, perm fs.FileMode, excl bool) error { + oflags := os.O_WRONLY | os.O_CREATE | os.O_TRUNC + if excl { + oflags |= os.O_EXCL + } + f, err := os.OpenFile(name, oflags, perm) + if err != nil { + return err + } + defer f.Close() + if _, err := f.Write(data); err != nil { + return fmt.Errorf("cannot write file contents: %v", err) + } + return nil +} + // setup sets up the test execution temporary directory and environment. // It returns the comment section of the txtar archive. func (ts *TestScript) setup() string { @@ -433,7 +454,12 @@ func (ts *TestScript) setup() string { name := ts.MkAbs(ts.expand(f.Name)) ts.scriptFiles[name] = f.Name ts.Check(os.MkdirAll(filepath.Dir(name), 0o777)) - ts.Check(ioutil.WriteFile(name, f.Data, 0o666)) + switch err := writeFile(name, f.Data, 0o666, ts.params.RequireUniqueNames); { + case ts.params.RequireUniqueNames && errors.Is(err, fs.ErrExist): + ts.Check(fmt.Errorf("%s would overwrite %s (because RequireUniqueNames is enabled)", f.Name, name)) + default: + ts.Check(err) + } } // Run any user-defined setup. if ts.params.Setup != nil { diff --git a/testscript/testscript_test.go b/testscript/testscript_test.go index 95f06ab5..53f420a1 100644 --- a/testscript/testscript_test.go +++ b/testscript/testscript_test.go @@ -213,6 +213,7 @@ func TestScripts(t *testing.T) { fset := flag.NewFlagSet("testscript", flag.ContinueOnError) fUpdate := fset.Bool("update", false, "update scripts when cmp fails") fExplicitExec := fset.Bool("explicit-exec", false, "require explicit use of exec for commands") + fUniqueNames := fset.Bool("unique-names", false, "require unique names in txtar archive") fVerbose := fset.Bool("v", false, "be verbose with output") fContinue := fset.Bool("continue", false, "continue on error") if err := fset.Parse(args); err != nil { @@ -229,6 +230,7 @@ func TestScripts(t *testing.T) { Dir: ts.MkAbs(dir), UpdateScripts: *fUpdate, RequireExplicitExec: *fExplicitExec, + RequireUniqueNames: *fUniqueNames, Cmds: map[string]func(ts *TestScript, neg bool, args []string){ "some-param-cmd": func(ts *TestScript, neg bool, args []string) { },