diff --git a/testscript/testdata/testscript_duplicate_name.txt b/testscript/testdata/testscript_duplicate_name.txt new file mode 100644 index 00000000..231c71c0 --- /dev/null +++ b/testscript/testdata/testscript_duplicate_name.txt @@ -0,0 +1,34 @@ +# Check that RequireUniqueNames works; +# it should reject txtar archives with duplicate names as defined by the host system. + +unquote scripts-case-insensitive/testscript.txt +unquote scripts-case-sensitive/testscript.txt +unquote scripts-normalized/testscript.txt + +testscript scripts-case-insensitive +testscript scripts-case-sensitive +testscript scripts-normalized + +[darwin] ! testscript -unique-names scripts-case-insensitive +[darwin] stdout 'duplicate name' + +[linux] testscript -unique-names scripts-case-insensitive + +[windows] ! testscript -unique-names scripts-case-insensitive +[windows] stdout 'duplicate name' + +! testscript -unique-names scripts-case-sensitive +stdout 'duplicate name' + +! testscript -unique-names scripts-normalized +stdout 'duplicate name' + +-- scripts-case-insensitive/testscript.txt -- +>-- File -- +>-- file -- +-- scripts-case-sensitive/testscript.txt -- +>-- file -- +>-- file -- +-- scripts-normalized/testscript.txt -- +>-- file -- +>-- dir/../file -- \ No newline at end of file diff --git a/testscript/testscript.go b/testscript/testscript.go index 6f43bd6c..e449646e 100644 --- a/testscript/testscript.go +++ b/testscript/testscript.go @@ -10,9 +10,11 @@ package testscript import ( "bytes" "context" + "errors" "flag" "fmt" "go/build" + "io/fs" "io/ioutil" "os" "os/exec" @@ -161,6 +163,10 @@ type Params struct { // consistency across test scripts as well as keep separate process // executions explicit. RequireExplicitExec bool + + // RequireUniqueNames requires that names in the txtar archive are unique. + // By default, later entries silently overwrite earlier ones. + RequireUniqueNames bool } // RunDir runs the tests in the given directory. All files in dir with a ".txt" @@ -364,6 +370,11 @@ func (ts *TestScript) setup() string { ts.archive = a for _, f := range a.Files { name := ts.MkAbs(ts.expand(f.Name)) + if ts.params.RequireUniqueNames { + if _, err := os.Lstat(name); !errors.Is(err, fs.ErrNotExist) { + ts.Fatalf("%s: duplicate name", f.Name) + } + } 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 c09cabb8..84cbc536 100644 --- a/testscript/testscript_test.go +++ b/testscript/testscript_test.go @@ -183,6 +183,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("verbose", false, "be verbose with output") if err := fset.Parse(args); err != nil { ts.Fatalf("failed to parse args for testscript: %v", err) @@ -204,6 +205,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) { },