From a2234479882b4782980dd4bcc3f8f132b88d679e Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sat, 22 Aug 2020 17:59:28 +0200 Subject: [PATCH] Improve testscripts --- go.mod | 3 + go.sum | 5 +- main_test.go | 236 ++++++++++++++++++++++++------- testdata/scripts/applyremove.txt | 13 +- testdata/scripts/archive.txt | 10 +- testdata/scripts/cat.txt | 5 +- testdata/scripts/chattr.txt | 5 +- testdata/scripts/edit.txt | 5 +- testdata/scripts/forget.txt | 15 +- testdata/scripts/init.txt | 4 +- testdata/scripts/managed.txt | 20 ++- testdata/scripts/purge.txt | 4 +- testdata/scripts/sourcepath.txt | 5 +- testdata/scripts/updategit.txt | 6 +- 14 files changed, 229 insertions(+), 107 deletions(-) diff --git a/go.mod b/go.mod index 9362af658eb..0488c66d9a8 100644 --- a/go.mod +++ b/go.mod @@ -52,3 +52,6 @@ require ( gopkg.in/ini.v1 v1.60.0 // indirect gopkg.in/yaml.v2 v2.3.0 ) + +// Temporary while waiting for https://github.com/rogpeppe/go-internal/pull/106 to be merged. +replace github.com/rogpeppe/go-internal => github.com/twpayne/go-internal v1.5.3-0.20200706163000-4426ab554b0a diff --git a/go.sum b/go.sum index e4c5a2b1850..6d73f5f1745 100644 --- a/go.sum +++ b/go.sum @@ -291,9 +291,6 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= @@ -348,6 +345,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/twpayne/go-internal v1.5.3-0.20200706163000-4426ab554b0a h1:Vq/UMXTdWUDhrmgvcioHYtz/IavukmThI08zxYektl4= +github.com/twpayne/go-internal v1.5.3-0.20200706163000-4426ab554b0a/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/twpayne/go-shell v0.3.0 h1:nhDM9cQTqWwA0jGPOnqzsw8lke2jcKpvxDgnDO8Lq9o= github.com/twpayne/go-shell v0.3.0/go.mod h1:H/gzux0DOH5jsjQSHXs6rs2Onxy+V4j6ycZTOulC0l8= github.com/twpayne/go-vfs v1.0.1/go.mod h1:OIXA6zWkcn7Jk46XT7ceYqBMeIkfzJ8WOBhGJM0W4y8= diff --git a/main_test.go b/main_test.go index fee90af9c31..7c1db3f2516 100644 --- a/main_test.go +++ b/main_test.go @@ -6,80 +6,109 @@ import ( "os" "path/filepath" "runtime" + "strconv" "strings" "testing" "github.com/rogpeppe/go-internal/testscript" "github.com/twpayne/go-vfs" "github.com/twpayne/go-vfs/vfst" + + "github.com/twpayne/chezmoi/cmd" ) //nolint:interfacer func TestMain(m *testing.M) { os.Exit(testscript.RunMain(m, map[string]func() int{ - "chezmoi": testRun, + "chezmoi": func() int { + if err := cmd.Execute(); err != nil { + if s := err.Error(); s != "" { + fmt.Fprintf(os.Stderr, "chezmoi: %s\n", s) + } + return 1 + } + return 0 + }, })) } -func TestChezmoi(t *testing.T) { - if testing.Short() { - t.Skip("skipping integration tests in short mode") - } +func TestScript(t *testing.T) { testscript.Run(t, testscript.Params{ Dir: filepath.Join("testdata", "scripts"), Cmds: map[string]func(*testscript.TestScript, bool, []string){ - "chhome": chHome, - "edit": edit, + "chhome": cmdChHome, + "cmpmod": cmdCmpMod, + "edit": cmdEdit, + "mkfile": cmdMkFile, + "mkhomedir": cmdMkHomeDir, + "mksourcedir": cmdMkSourceDir, }, Condition: func(cond string) (bool, error) { switch cond { case "windows": return runtime.GOOS == "windows", nil default: - return false, fmt.Errorf("unknown condition: %s", cond) + return false, fmt.Errorf("%s: unknown condition", cond) } }, - Setup: setup, + Setup: setup, + UpdateScripts: os.Getenv("CHEZMOIUPDATESCRIPTS") != "", }) } -func testRun() int { - if err := run(); err != nil { - if s := err.Error(); s != "" { - fmt.Printf("chezmoi: %s\n", s) - } - return 1 - } - return 0 -} - -// chHome changes the home directory to its argument, creating the directory if -// it does not already exists. It updates the HOME environment variable, and, if -// running on Windows, USERPROFILE too. -func chHome(ts *testscript.TestScript, neg bool, args []string) { +// cmdChHome changes the home directory to its argument, creating the directory +// if it does not already exists. It updates the HOME environment variable, and, +// if running on Windows, USERPROFILE too. +func cmdChHome(ts *testscript.TestScript, neg bool, args []string) { if neg { - ts.Fatalf("unsupported ! chhome") + ts.Fatalf("unsupported: ! chhome") } if len(args) != 1 { ts.Fatalf("usage: chhome dir") } - homeDir := args[0] - if !filepath.IsAbs(homeDir) { - homeDir = filepath.Join(ts.Getenv("WORK"), homeDir) - } + var ( + homeDir = ts.MkAbs(args[0]) + chezmoiConfigDir = filepath.Join(homeDir, ".config", "chezmoi") + chezmoiSourceDir = filepath.Join(homeDir, ".local", "share", "chezmoi") + ) ts.Check(os.MkdirAll(homeDir, 0o777)) ts.Setenv("HOME", homeDir) + ts.Setenv("CHEZMOICONFIGDIR", chezmoiConfigDir) + ts.Setenv("CHEZMOISOURCEDIR", chezmoiSourceDir) if runtime.GOOS == "windows" { ts.Setenv("USERPROFILE", homeDir) } - ts.Setenv("CHEZMOICONFIGDIR", filepath.Join(homeDir, ".config", "chezmoi")) - ts.Setenv("CHEZMOISOURCEDIR", filepath.Join(homeDir, ".local", "share", "chezmoi")) } -// edit edits all of its arguments by appending "# edited\n" to them. -func edit(ts *testscript.TestScript, neg bool, args []string) { +// cmdCmpMod compares modes. +func cmdCmpMod(ts *testscript.TestScript, neg bool, args []string) { + if len(args) != 2 { + ts.Fatalf("usage: cmpmod mode path") + } + mode64, err := strconv.ParseUint(args[0], 8, 32) + if err != nil || os.FileMode(mode64)&os.ModePerm != os.FileMode(mode64) { + ts.Fatalf("invalid mode: %s", args[0]) + } + if runtime.GOOS == "windows" { + return + } + info, err := os.Stat(args[1]) + if err != nil { + ts.Fatalf("%s: %v", args[1], err) + } + equal := info.Mode()&os.ModePerm == os.FileMode(mode64) + if neg && equal { + ts.Fatalf("%s unexpectedly has mode %03o", args[1], info.Mode()&os.ModePerm) + } + if !neg && !equal { + ts.Fatalf("%s has mode %03o, expected %03o", args[1], info.Mode()&os.ModePerm, os.FileMode(mode64)) + } +} + +// cmdEdit edits all of its arguments by appending "# edited\n" to them. +func cmdEdit(ts *testscript.TestScript, neg bool, args []string) { if neg { - ts.Fatalf("unsupported ! edit") + ts.Fatalf("unsupported: ! edit") } for _, arg := range args { filename := ts.MkAbs(arg) @@ -95,6 +124,115 @@ func edit(ts *testscript.TestScript, neg bool, args []string) { } } +// cmdMkFile creates empty files. +func cmdMkFile(ts *testscript.TestScript, neg bool, args []string) { + if neg { + ts.Fatalf("unsupported: ! mkfile") + } + perm := os.FileMode(0o666) + if len(args) >= 1 && strings.HasPrefix(args[0], "-perm=") { + permStr := strings.TrimPrefix(args[0], "-perm=") + permUint32, err := strconv.ParseUint(permStr, 8, 32) + if err != nil { + ts.Fatalf("%s: bad permissions", permStr) + } + perm = os.FileMode(permUint32) + args = args[1:] + } + for _, arg := range args { + filename := ts.MkAbs(arg) + _, err := os.Lstat(filename) + switch { + case err == nil: + ts.Fatalf("%s: already exists", arg) + case !os.IsNotExist(err): + ts.Fatalf("%s: %v", arg, err) + } + if err := ioutil.WriteFile(filename, nil, perm); err != nil { + ts.Fatalf("%s: %v", arg, err) + } + } +} + +// cmdMkHomeDir makes and populates a home directory. +func cmdMkHomeDir(ts *testscript.TestScript, neg bool, args []string) { + if neg { + ts.Fatalf("unsupported: ! mkhomedir") + } + if len(args) > 1 { + ts.Fatalf(("usage: mkhomedir [path]")) + } + path := ts.Getenv("HOME") + if len(args) > 0 { + path = ts.MkAbs(args[0]) + } + workDir := ts.Getenv("WORK") + relPath, err := filepath.Rel(workDir, path) + ts.Check(err) + if err := vfst.NewBuilder().Build(vfs.NewPathFS(vfs.OSFS, workDir), map[string]interface{}{ + relPath: map[string]interface{}{ + ".bashrc": "# contents of .bashrc\n", + ".binary": &vfst.File{ + Perm: 0o755, + Contents: []byte("#!/bin/sh\n"), + }, + ".exists": "# contents of .exists\n", + ".gitconfig": "" + + "[user]\n" + + " email = you@example.com\n" + + " name = Your Name\n", + ".hushlogin": "", + ".ssh": &vfst.Dir{ + Perm: 0o700, + Entries: map[string]interface{}{ + "config": "# contents of .ssh/config\n", + }, + }, + ".symlink": &vfst.Symlink{ + Target: ".bashrc", + }, + }, + }); err != nil { + ts.Fatalf("mkhomedir: %v", err) + } +} + +// cmdMkSourceDir makes and populates a source directory. +func cmdMkSourceDir(ts *testscript.TestScript, neg bool, args []string) { + if neg { + ts.Fatalf("unsupported: ! mksourcedir") + } + if len(args) > 1 { + ts.Fatalf("usage: mksourcedir [path]") + } + sourceDir := ts.Getenv("CHEZMOISOURCEDIR") + if len(args) > 0 { + sourceDir = ts.MkAbs(args[0]) + } + workDir := ts.Getenv("WORK") + relPath, err := filepath.Rel(workDir, sourceDir) + ts.Check(err) + err = vfst.NewBuilder().Build(vfs.NewPathFS(vfs.OSFS, workDir), map[string]interface{}{ + relPath: map[string]interface{}{ + "dot_absent": "", + "empty_dot_hushlogin": "", + "executable_dot_binary": "#!/bin/sh\n", + "dot_bashrc": "# contents of .bashrc\n", + "dot_gitconfig.tmpl": "" + + "[user]\n" + + " email = {{ \"you@example.com\" }}\n" + + " name = Your Name\n", + "private_dot_ssh": map[string]interface{}{ + "config": "# contents of .ssh/config\n", + }, + "symlink_dot_symlink": ".bashrc\n", + }, + }) + if err != nil { + ts.Fatalf("mksourcedir: %v", err) + } +} + func setup(env *testscript.Env) error { var ( binDir = filepath.Join(env.WorkDir, "bin") @@ -118,31 +256,19 @@ func setup(env *testscript.Env) error { env.Setenv("SHELL", filepath.Join(binDir, "shell")) } - // Fix permissions on the source directory, if it exists. - _ = os.Chmod(chezmoiSourceDir, 0o700) - - // Fix permissions on any files in the bin directory. - infos, err := ioutil.ReadDir(binDir) - if err == nil { - for _, info := range infos { - if err := os.Chmod(filepath.Join(binDir, info.Name()), 0o755); err != nil { - return err + if runtime.GOOS != "windows" { + // Fix permissions on any files in the bin directory. + infos, err := ioutil.ReadDir(binDir) + if err == nil { + for _, info := range infos { + if err := os.Chmod(filepath.Join(binDir, info.Name()), 0o755); err != nil { + return err + } } } } - root := map[string]interface{}{ - "/home/user": map[string]interface{}{ - // .gitconfig is populated with a user and email to avoid warnings - // from git. - ".gitconfig": strings.Join([]string{ - `[user]`, - ` name = Username`, - ` email = user@home.org`, - }, "\n"), - }, - } - + root := make(map[string]interface{}) switch runtime.GOOS { case "windows": root["/bin"] = map[string]interface{}{ @@ -150,7 +276,7 @@ func setup(env *testscript.Env) error { // the end of each file. "editor.cmd": &vfst.File{ Perm: 0o755, - Contents: []byte(`@for %%x in (%*) do echo # edited>>%%x`), + Contents: []byte(`@for %%x in (%*) do echo # edited >> %%x`), }, } default: diff --git a/testdata/scripts/applyremove.txt b/testdata/scripts/applyremove.txt index 74242823ca4..d4af53bfda2 100644 --- a/testdata/scripts/applyremove.txt +++ b/testdata/scripts/applyremove.txt @@ -1,12 +1,7 @@ -exists $HOME${/}.bashrc -exists $HOME${/}.inputrc +exists $HOME/.hushlogin chezmoi apply --remove -! exists $HOME${/}.bashrc -exists $HOME${/}.inputrc +! exists $HOME/.hushlogin --- home/user/.bashrc -- -# contents of .bashrc --- home/user/.inputrc -- -# contents of .inputrc +-- home/user/.hushlogin -- -- home/user/.local/share/chezmoi/.chezmoiremove -- -.bashrc +.hushlogin diff --git a/testdata/scripts/archive.txt b/testdata/scripts/archive.txt index 1f02494d416..3d96fcd09ac 100644 --- a/testdata/scripts/archive.txt +++ b/testdata/scripts/archive.txt @@ -1,15 +1,17 @@ [windows] stop 'https://github.com/twpayne/chezmoi/issues/745' [!exec:tar] stop +mksourcedir + chezmoi archive --output=user.tar exec tar -tf user.tar cmp stdout golden/archive -- golden/archive -- .bashrc +.binary +.gitconfig +.hushlogin .ssh/ .ssh/config --- home/user/.local/share/chezmoi/dot_bashrc -- -# contents of .bashrc --- home/user/.local/share/chezmoi/private_dot_ssh/config -- -# contents .ssh/config +.symlink diff --git a/testdata/scripts/cat.txt b/testdata/scripts/cat.txt index 809818167b5..13118e4dc8d 100644 --- a/testdata/scripts/cat.txt +++ b/testdata/scripts/cat.txt @@ -1,5 +1,4 @@ +mksourcedir + chezmoi cat $HOME${/}.bashrc stdout '# contents of .bashrc' - --- home/user/.local/share/chezmoi/dot_bashrc -- -# contents of .bashrc diff --git a/testdata/scripts/chattr.txt b/testdata/scripts/chattr.txt index 50fc82a314a..7810327ee93 100644 --- a/testdata/scripts/chattr.txt +++ b/testdata/scripts/chattr.txt @@ -1,3 +1,5 @@ +mksourcedir + chezmoi chattr +empty $HOME${/}.bashrc exists ${CHEZMOISOURCEDIR}${/}empty_dot_bashrc @@ -15,6 +17,3 @@ exists ${CHEZMOISOURCEDIR}${/}private_dot_bashrc chezmoi chattr nop $HOME${/}.bashrc exists ${CHEZMOISOURCEDIR}${/}dot_bashrc - --- home/user/.local/share/chezmoi/dot_bashrc -- -# contents of .bashrc diff --git a/testdata/scripts/edit.txt b/testdata/scripts/edit.txt index 5e166fc43bc..f8dce203ed7 100644 --- a/testdata/scripts/edit.txt +++ b/testdata/scripts/edit.txt @@ -1,5 +1,4 @@ +mksourcedir + chezmoi edit $HOME/.bashrc grep -count=1 '# edited' $CHEZMOISOURCEDIR/dot_bashrc - --- home/user/.local/share/chezmoi/dot_bashrc -- -# contents of .bashrc diff --git a/testdata/scripts/forget.txt b/testdata/scripts/forget.txt index c159b4f61dd..d6094e746c0 100644 --- a/testdata/scripts/forget.txt +++ b/testdata/scripts/forget.txt @@ -1,17 +1,4 @@ -edit ${CHEZMOISOURCEDIR}/dot_bashrc -grep -count=1 '# edited' ${CHEZMOISOURCEDIR}${/}dot_bashrc - -chezmoi apply -grep -count=1 '# edited' $HOME${/}.bashrc - -edit ${CHEZMOISOURCEDIR}${/}dot_bashrc -grep -count=2 '# edited' ${CHEZMOISOURCEDIR}${/}dot_bashrc +mksourcedir chezmoi forget $HOME${/}.bashrc ! exists ${CHEZMOISOURCEDIR}${/}dot_bashrc - -chezmoi apply -grep -count=1 '# edited' $HOME${/}.bashrc - --- home/user/.local/share/chezmoi/dot_bashrc -- -# contents of .bashrc diff --git a/testdata/scripts/init.txt b/testdata/scripts/init.txt index 22ce2ed8f04..d3415f90704 100644 --- a/testdata/scripts/init.txt +++ b/testdata/scripts/init.txt @@ -1,5 +1,7 @@ [!exec:git] stop +mkhomedir + # test that chezmoi init creates a git repo chezmoi init exists ${CHEZMOISOURCEDIR}${/}.git @@ -27,8 +29,6 @@ chhome home4${/}user chezmoi init --source=${HOME}/dotfiles --verbose file://$WORK/nonexistentrepo exists ${CHEZMOICONFIGDIR}/chezmoi.toml --- home/user/.bashrc -- -# contents of .bashrc -- home4/user/dotfiles/.git/.keep -- -- home4/user/dotfiles/.chezmoi.toml.tmpl -- [data] diff --git a/testdata/scripts/managed.txt b/testdata/scripts/managed.txt index 4e1ca72081d..a9ea5b80aa0 100644 --- a/testdata/scripts/managed.txt +++ b/testdata/scripts/managed.txt @@ -1,5 +1,7 @@ [windows] stop 'https://github.com/twpayne/chezmoi/issues/745' +mksourcedir + chezmoi managed cmpenv stdout golden/managed @@ -9,16 +11,26 @@ cmpenv stdout golden/managed-dirs chezmoi --include=files managed cmpenv stdout golden/managed-files +chezmoi --include=symlinks managed +cmpenv stdout golden/managed-symlinks + -- golden/managed -- +$HOME/.absent $HOME/.bashrc +$HOME/.binary +$HOME/.gitconfig +$HOME/.hushlogin $HOME/.ssh $HOME/.ssh/config +$HOME/.symlink -- golden/managed-dirs -- $HOME/.ssh -- golden/managed-files -- +$HOME/.absent $HOME/.bashrc +$HOME/.binary +$HOME/.gitconfig +$HOME/.hushlogin $HOME/.ssh/config --- home/user/.local/share/chezmoi/dot_bashrc -- -# contents of .bashrc --- home/user/.local/share/chezmoi/dot_ssh/config -- -# contents of .ssh/config +-- golden/managed-symlinks -- +$HOME/.symlink diff --git a/testdata/scripts/purge.txt b/testdata/scripts/purge.txt index a6b579da8ae..7ab99b8d752 100644 --- a/testdata/scripts/purge.txt +++ b/testdata/scripts/purge.txt @@ -1,3 +1,5 @@ +mksourcedir + chezmoi purge --force ! exists ${CHEZMOICONFIGDIR} ! exists ${CHEZMOISOURCEDIR} @@ -5,5 +7,3 @@ chezmoi purge --force -- home/user/.config/chezmoi/chezmoi.toml -- [data] email = "user@home.org" --- home/user/.local/share/chezmoi/dot_bashrc -- -# contents of .bashrc diff --git a/testdata/scripts/sourcepath.txt b/testdata/scripts/sourcepath.txt index 95739eeb8af..74300c55be1 100644 --- a/testdata/scripts/sourcepath.txt +++ b/testdata/scripts/sourcepath.txt @@ -1,9 +1,8 @@ +mksourcedir + chezmoi source-path stdout ${CHEZMOISOURCEDIR@R} chezmoi source-path $HOME${/}.bashrc env WANT=${CHEZMOISOURCEDIR}${/}dot_bashrc stdout ${WANT@R} - --- home/user/.local/share/chezmoi/dot_bashrc -- -# contents of .bashrc diff --git a/testdata/scripts/updategit.txt b/testdata/scripts/updategit.txt index 9e18d49952a..8ac9039f438 100644 --- a/testdata/scripts/updategit.txt +++ b/testdata/scripts/updategit.txt @@ -1,5 +1,7 @@ [!exec:git] stop +mkhomedir + # create a repo chezmoi init chezmoi add $HOME${/}.bashrc @@ -9,7 +11,7 @@ chezmoi git -- commit -m 'Add dot_bashrc' # test chezmoi init --apply chhome home2${/}user chezmoi init --apply file://$WORK/home/user/.local/share/chezmoi -cmp $HOME${/}.bashrc $WORK${/}home${/}user${/}.bashrc +cmp -text $HOME${/}.bashrc $WORK${/}home${/}user${/}.bashrc # create a new commit chhome home${/}user @@ -23,4 +25,4 @@ chezmoi update grep '# edited' $HOME${/}.bashrc -- home/user/.bashrc -- -# contents of .bashrc +# contents of .bashrc \ No newline at end of file