Repository: twpayne/chezmoi
Pull request #357 (2019-06-20 19:28:53)

Add tests for issue 353


Patch:
diff --git a/cmd/doctor.go b/cmd/doctor.go
index 3a189ab77..f54758abe 100644
--- a/cmd/doctor.go
+++ b/cmd/doctor.go
@@ -77,6 +77,13 @@ type doctorSuspiciousFilesCheck struct {
 
 type doctorVersionCheck struct{}
 
+var gpgBinaryCheck = &doctorBinaryCheck{
+	name:          "GnuPG",
+	binaryName:    "gpg",
+	versionArgs:   []string{"--version"},
+	versionRegexp: regexp.MustCompile(`^gpg \(.*?\) (\d+\.\d+\.\d+)`),
+}
+
 func init() {
 	rootCmd.AddCommand(doctorCmd)
 }
@@ -135,12 +142,7 @@ func (c *Config) runDoctorCmd(fs vfs.FS, args []string) error {
 			binaryName: c.Merge.Command,
 		},
 		vcsCommandCheck,
-		&doctorBinaryCheck{
-			name:          "GnuPG",
-			binaryName:    "gpg",
-			versionArgs:   []string{"--version"},
-			versionRegexp: regexp.MustCompile(`^gpg \(GnuPG\) (\d+\.\d+\.\d+)`),
-		},
+		gpgBinaryCheck,
 		&doctorBinaryCheck{
 			name:          "1Password CLI",
 			binaryName:    c.Onepassword.Command,
@@ -236,13 +238,9 @@ func (c *doctorBinaryCheck) Check() (bool, error) {
 		if err != nil {
 			return false, err
 		}
-		m := c.versionRegexp.FindSubmatch(output)
-		if m == nil {
-			return false, fmt.Errorf("%s: could not extract version from %q", c.path, output)
-		}
-		version, err := semver.NewVersion(string(m[1]))
+		version, err := c.getVersionFromOutput(output)
 		if err != nil {
-			return false, err
+			return false, nil
 		}
 		c.version = version
 		if c.minVersion != nil && c.version.LessThan(*c.minVersion) {
@@ -280,6 +278,14 @@ func (c *doctorBinaryCheck) Skip() bool {
 	return false
 }
 
+func (c *doctorBinaryCheck) getVersionFromOutput(output []byte) (*semver.Version, error) {
+	m := c.versionRegexp.FindSubmatch(output)
+	if m == nil {
+		return nil, fmt.Errorf("%s: could not extract version from %q", c.path, output)
+	}
+	return semver.NewVersion(string(m[1]))
+}
+
 func (c *doctorDirectoryCheck) Check() (bool, error) {
 	c.info, c.err = os.Stat(c.path)
 	if c.err != nil && os.IsNotExist(c.err) {
diff --git a/cmd/upgrade.go b/cmd/upgrade.go
index f9a324c7d..c66af75cd 100644
--- a/cmd/upgrade.go
+++ b/cmd/upgrade.go
@@ -23,7 +23,7 @@ import (
 	"syscall"
 
 	"github.com/coreos/go-semver/semver"
-	"github.com/google/go-github/v26/github"
+	"github.com/google/go-github/v25/github"
 	"github.com/spf13/cobra"
 	"github.com/twpayne/chezmoi/lib/chezmoi"
 	vfs "github.com/twpayne/go-vfs"
diff --git a/docs/INSTALL.md b/docs/INSTALL.md
index a6cc42851..3213842d5 100644
--- a/docs/INSTALL.md
+++ b/docs/INSTALL.md
@@ -21,7 +21,7 @@ Install chezmoi with a single command.
 | OS    | Distribution | Method   | Command                             |
 | ----- | ------------ | ---------| ----------------------------------- |
 | Linux | -            | snap     | `snap install chezmoi --classic`    |
-| Linux | Arch         | pacman   | `pacman -S chezmoi`                |
+| Linux | Arch         | pacman   | `pacman -Ss chezmoi`                |
 | macOS | -            | Homebrew | `brew install twpayne/taps/chezmoi` |
 
 ## Pre-built Linux packages
diff --git a/go.mod b/go.mod
index 41ee985c3..5a138de75 100644
--- a/go.mod
+++ b/go.mod
@@ -10,7 +10,7 @@ require (
 	github.com/coreos/go-semver v0.2.0
 	github.com/danieljoos/wincred v1.0.1 // indirect
 	github.com/godbus/dbus v4.1.0+incompatible // indirect
-	github.com/google/go-github/v26 v26.0.4
+	github.com/google/go-github/v25 v25.0.1
 	github.com/google/renameio v0.1.0
 	github.com/google/uuid v1.1.0 // indirect
 	github.com/huandu/xstrings v1.2.0 // indirect
@@ -30,7 +30,7 @@ require (
 	github.com/twpayne/go-vfsafero v1.0.0
 	github.com/twpayne/go-xdg/v3 v3.1.0
 	github.com/zalando/go-keyring v0.0.0-20180221093347-6d81c293b3fb
-	go.etcd.io/bbolt v1.3.3-0.20190510211640-4af6cfab7010
+	go.etcd.io/bbolt v1.3.3
 	golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
 	golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be
 	golang.org/x/sys v0.0.0-20190310054646-10058d7d4faa // indirect
diff --git a/go.sum b/go.sum
index d02bdbe7c..54319aa64 100644
--- a/go.sum
+++ b/go.sum
@@ -25,8 +25,8 @@ github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7
 github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
 github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/google/go-github/v26 v26.0.4 h1:tKeV5nmkQEODmOwmzN12ArOnF69QHi7+niPgrjVASC0=
-github.com/google/go-github/v26 v26.0.4/go.mod h1:v6/FmX9au22j4CtYxnMhJJkP+JfOQDXALk7hI+MPDNM=
+github.com/google/go-github/v25 v25.0.1 h1:s405kPD52lKa1MVxiEumod/E6/+0pvQ8Ed/sT65DjKc=
+github.com/google/go-github/v25 v25.0.1/go.mod h1:6z5pC69qHtrPJ0sXPsj4BLnd82b+r6sLB7qcBoRZqpw=
 github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
 github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
@@ -97,8 +97,8 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 github.com/zalando/go-keyring v0.0.0-20180221093347-6d81c293b3fb h1:tXbazu9ZlecQbyCczvA22mWj+lw/36Bdwxapk8v7e7s=
 github.com/zalando/go-keyring v0.0.0-20180221093347-6d81c293b3fb/go.mod h1:XlXBIfkGawHNVOHlenOaBW7zlfCh8LovwjOgjamYnkQ=
-go.etcd.io/bbolt v1.3.3-0.20190510211640-4af6cfab7010 h1:XWsASCLIxZzlppASNxGVCVRoGd8z1e7Kr5EXUAMPsjE=
-go.etcd.io/bbolt v1.3.3-0.20190510211640-4af6cfab7010/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
+go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=


Test patch:
diff --git a/cmd/apply_test.go b/cmd/apply_test.go
index 628c5f633..b17a7335a 100644
--- a/cmd/apply_test.go
+++ b/cmd/apply_test.go
@@ -4,11 +4,13 @@ import (
 	"io/ioutil"
 	"os"
 	"path/filepath"
+	"strings"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 	"github.com/twpayne/chezmoi/lib/chezmoi"
+	vfs "github.com/twpayne/go-vfs"
 	"github.com/twpayne/go-vfs/vfst"
 )
 
@@ -126,40 +128,82 @@ func TestApplyScript(t *testing.T) {
 		require.NoError(t, os.RemoveAll(tempDir))
 	}()
 	for _, tc := range []struct {
-		name     string
-		root     interface{}
-		data     map[string]interface{}
-		evidence string
+		name  string
+		root  interface{}
+		data  map[string]interface{}
+		tests []vfst.Test
 	}{
 		{
 			name: "simple",
 			root: map[string]interface{}{
-				"/home/user/.local/share/chezmoi/run_true": "#!/bin/sh\ntouch " + filepath.Join(tempDir, "simple") + "\n",
+				"/home/user/.local/share/chezmoi/run_true": "#!/bin/sh\necho foo >>" + filepath.Join(tempDir, "evidence") + "\n",
+			},
+			tests: []vfst.Test{
+				vfst.TestPath(filepath.Join(tempDir, "evidence"),
+					vfst.TestModeIsRegular,
+					vfst.TestContentsString("foo\nfoo\nfoo\n"),
+				),
 			},
-			evidence: "simple",
 		},
 		{
 			name: "simple_once",
 			root: map[string]interface{}{
-				"/home/user/.local/share/chezmoi/run_once_true": "#!/bin/sh\ntouch " + filepath.Join(tempDir, "simple_once") + "\n",
+				"/home/user/.local/share/chezmoi/run_once_true": "#!/bin/sh\necho foo >>" + filepath.Join(tempDir, "evidence") + "\n",
+			},
+			tests: []vfst.Test{
+				vfst.TestPath(filepath.Join(tempDir, "evidence"),
+					vfst.TestModeIsRegular,
+					vfst.TestContentsString("foo\n"),
+				),
 			},
-			evidence: "simple_once",
 		},
 		{
 			name: "template",
 			root: map[string]interface{}{
-				"/home/user/.local/share/chezmoi/run_true.tmpl": "#!/bin/sh\ntouch {{ .Evidence }}\n",
+				"/home/user/.local/share/chezmoi/run_true.tmpl": "#!/bin/sh\necho {{ .Foo }} >>" + filepath.Join(tempDir, "evidence") + "\n",
 			},
 			data: map[string]interface{}{
-				"Evidence": filepath.Join(tempDir, "template"),
+				"Foo": "foo",
+			},
+			tests: []vfst.Test{
+				vfst.TestPath(filepath.Join(tempDir, "evidence"),
+					vfst.TestModeIsRegular,
+					vfst.TestContentsString("foo\nfoo\nfoo\n"),
+				),
+			},
+		},
+		{
+			name: "issue_353",
+			root: map[string]interface{}{
+				"/home/user/.local/share/chezmoi": map[string]interface{}{
+					"run_050_giraffe":       "#!/usr/bin/env bash\necho giraffe >>" + filepath.Join(tempDir, "evidence") + "\n",
+					"run_150_elephant":      "#!/usr/bin/env bash\necho elephant >>" + filepath.Join(tempDir, "evidence") + "\n",
+					"run_once_100_miauw.sh": "#!/usr/bin/env bash\necho miauw >>" + filepath.Join(tempDir, "evidence") + "\n",
+				},
+			},
+			tests: []vfst.Test{
+				vfst.TestPath(filepath.Join(tempDir, "evidence"),
+					vfst.TestModeIsRegular,
+					vfst.TestContentsString(strings.Join([]string{
+						"giraffe\n",
+						"miauw\n",
+						"elephant\n",
+						"giraffe\n",
+						"elephant\n",
+						"giraffe\n",
+						"elephant\n",
+					}, "")),
+				),
 			},
-			evidence: "template",
 		},
 	} {
 		t.Run(tc.name, func(t *testing.T) {
-			fs, cleanup, err := vfst.NewTestFS(tc.root)
-			require.NoError(t, err)
-			defer cleanup()
+			fs := vfs.NewPathFS(vfs.OSFS, tempDir)
+			defer func() {
+				require.NoError(t, os.RemoveAll(tempDir))
+				require.NoError(t, os.Mkdir(tempDir, 0700))
+			}()
+			require.NoError(t, vfst.NewBuilder().Build(fs, tc.root))
 			persistentState, err := chezmoi.NewBoltPersistentState(fs, "/home/user/.config/chezmoi/chezmoistate.boltdb")
 			require.NoError(t, err)
 			c := &Config{
@@ -170,11 +214,12 @@ func TestApplyScript(t *testing.T) {
 				persistentState:   persistentState,
 				scriptStateBucket: []byte("script"),
 			}
-			assert.NoError(t, c.runApplyCmd(fs, nil))
-			evidencePath := filepath.Join(tempDir, tc.evidence)
-			_, err = os.Stat(evidencePath)
-			assert.NoError(t, err)
-			assert.NoError(t, os.Remove(evidencePath))
+			// Run apply three times. As chezmoi should be idempotent, the
+			// result should be the same each time.
+			for i := 0; i < 3; i++ {
+				assert.NoError(t, c.runApplyCmd(fs, nil))
+			}
+			vfst.RunTests(t, vfs.OSFS, "", tc.tests)
 		})
 	}
 }
diff --git a/cmd/doctor_test.go b/cmd/doctor_test.go
new file mode 100644
index 000000000..a5cc912d2
--- /dev/null
+++ b/cmd/doctor_test.go
@@ -0,0 +1,68 @@
+package cmd
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/coreos/go-semver/semver"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestDoctorBinaryCheck(t *testing.T) {
+	for _, tc := range []struct {
+		name     string
+		check    *doctorBinaryCheck
+		output   string
+		expected *semver.Version
+	}{
+		{
+			name:  "gnupg_2.2.16",
+			check: gpgBinaryCheck,
+			output: strings.Join([]string{
+				"gpg (GnuPG) 2.2.16",
+				"libgcrypt 1.8.4",
+				"Copyright (C) 2019 Free Software Foundation, Inc.",
+				"License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>",
+				"This is free software: you are free to change and redistribute it.",
+				"There is NO WARRANTY, to the extent permitted by law.",
+				"",
+				"Home: /Users/username/.gnupg",
+				"Supported algorithms:",
+				"Pubkey: RSA, ELG, DSA, ECDH, ECDSA, EDDSA",
+				"Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH,",
+				"        CAMELLIA128, CAMELLIA192, CAMELLIA256",
+				"Hash: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224",
+				"Compression: Uncompressed, ZIP, ZLIB, BZIP2",
+			}, "\n"),
+			expected: semver.New("2.2.16"),
+		},
+		{
+			name:  "issue_335",
+			check: gpgBinaryCheck,
+			output: strings.Join([]string{
+				"gpg (GnuPG/MacGPG2) 2.2.10",
+				"libgcrypt 1.8.3",
+				"Copyright (C) 2018 Free Software Foundation, Inc.",
+				"License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>",
+				"This is free software: you are free to change and redistribute it.",
+				"There is NO WARRANTY, to the extent permitted by law.",
+				"",
+				"Home: /Users/username/.gnupg",
+				"Supported algorithms:",
+				"Pubkey: RSA, ELG, DSA, ECDH, ECDSA, EDDSA",
+				"Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH,",
+				"        CAMELLIA128, CAMELLIA192, CAMELLIA256",
+				"Hash: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224",
+				"Compression: Uncompressed, ZIP, ZLIB, BZIP2",
+			}, "\n"),
+			expected: semver.New("2.2.10"),
+		},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			actual, err := tc.check.getVersionFromOutput([]byte(tc.output))
+			require.NoError(t, err)
+			assert.Equal(t, tc.expected, actual)
+		})
+	}
+}