diff --git a/testscript/testdata/testscript_duplicate_name.txt b/testscript/testdata/testscript_duplicate_name.txt new file mode 100644 index 00000000..54c6ce5f --- /dev/null +++ b/testscript/testdata/testscript_duplicate_name.txt @@ -0,0 +1,11 @@ +# 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 + +-- 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..6ad6e11e 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. @@ -431,6 +436,16 @@ func (ts *TestScript) setup() string { ts.archive = a for _, f := range a.Files { name := ts.MkAbs(ts.expand(f.Name)) + if ts.params.RequireUniqueNames { + switch _, err := os.Lstat(name); { + case err == nil: + ts.Fatalf("'%s' would overwrite '%s' (because RequireUniqueNames is enabled)", f.Name, name) + case errors.Is(err, fs.ErrNotExist): + // OK. + default: + ts.Fatalf("%s: %v", f.Name, err) + } + } ts.scriptFiles[name] = f.Name ts.Check(os.MkdirAll(filepath.Dir(name), 0o777)) ts.Check(ioutil.WriteFile(name, f.Data, 0o666)) 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) { },