Преглед на файлове

util: Change the API of WithTempDir and InTempDir.

They have been renamed to TestDir and InTestDir, and instead of taking a
callback, they return a cleanup function. This saves one level of
indentation in the caller.
Qi Xiao преди 5 години
родител
ревизия
1c133f6979

+ 25 - 24
daemon/daemon_test.go

@@ -8,31 +8,32 @@ import (
 )
 
 func TestDaemon(t *testing.T) {
-	util.InTempDir(func(string) {
-		serverDone := make(chan struct{})
-		go func() {
-			Serve("sock", "db")
-			close(serverDone)
-		}()
+	_, cleanup := util.InTestDir()
+	defer cleanup()
 
-		client := NewClient("sock")
-		for i := 0; i < 100; i++ {
-			client.ResetConn()
-			_, err := client.Version()
-			if err == nil {
-				break
-			} else if i == 99 {
-				t.Fatal("Failed to connect after 1s")
-			}
-			time.Sleep(10 * time.Millisecond)
-		}
+	serverDone := make(chan struct{})
+	go func() {
+		Serve("sock", "db")
+		close(serverDone)
+	}()
 
-		_, err := client.AddCmd("test cmd")
-		if err != nil {
-			t.Errorf("client.AddCmd -> error %v", err)
+	client := NewClient("sock")
+	for i := 0; i < 100; i++ {
+		client.ResetConn()
+		_, err := client.Version()
+		if err == nil {
+			break
+		} else if i == 99 {
+			t.Fatal("Failed to connect after 1s")
 		}
-		client.Close()
-		// Wait for server to quit before returning
-		<-serverDone
-	})
+		time.Sleep(10 * time.Millisecond)
+	}
+
+	_, err := client.AddCmd("test cmd")
+	if err != nil {
+		t.Errorf("client.AddCmd -> error %v", err)
+	}
+	client.Close()
+	// Wait for server to quit before returning
+	<-serverDone
 }

+ 34 - 33
edit/completion/complete_arg_unix_test.go

@@ -47,44 +47,45 @@ var complFilenameInnerTests = []struct {
 
 func TestComplFilenameInner(t *testing.T) {
 	os.Setenv("LS_COLORS", "rs=1:ex=2:di=4")
-	util.InTempDir(func(string) {
-		create("foo", 0600)
-		create(".vimrc", 0600)
-		create("bar", 0600)
+	_, cleanup := util.InTestDir()
+	defer cleanup()
 
-		create("elvish", 0700)
+	create("foo", 0600)
+	create(".vimrc", 0600)
+	create("bar", 0600)
 
-		mkdir("Documents", 0700)
-		mkdir(".elvish", 0700)
+	create("elvish", 0700)
 
-		for _, test := range complFilenameInnerTests {
-			var (
-				err   error
-				cands rawCandidates
-				gets  = make(chan rawCandidate)
-			)
-			go func() {
-				defer close(gets)
-				err = complFilenameInner(test.head, test.executableOnly, gets)
-			}()
-			for v := range gets {
-				cands = append(cands, v)
-			}
-			if err != nil {
-				t.Errorf("complFilenameInner(%v, %v) returns error %v, want nil",
-					test.head, test.executableOnly, err)
-			}
-			sort.Sort(cands)
-			if !reflect.DeepEqual(cands, test.wantCandidates) {
-				t.Errorf("complFilenameInner(%v, %v) returns %v, want %v",
-					test.head, test.executableOnly, cands, test.wantCandidates)
-				t.Log("returned candidates are:")
-				for _, cand := range cands {
-					t.Logf("%#v", cand)
-				}
+	mkdir("Documents", 0700)
+	mkdir(".elvish", 0700)
+
+	for _, test := range complFilenameInnerTests {
+		var (
+			err   error
+			cands rawCandidates
+			gets  = make(chan rawCandidate)
+		)
+		go func() {
+			defer close(gets)
+			err = complFilenameInner(test.head, test.executableOnly, gets)
+		}()
+		for v := range gets {
+			cands = append(cands, v)
+		}
+		if err != nil {
+			t.Errorf("complFilenameInner(%v, %v) returns error %v, want nil",
+				test.head, test.executableOnly, err)
+		}
+		sort.Sort(cands)
+		if !reflect.DeepEqual(cands, test.wantCandidates) {
+			t.Errorf("complFilenameInner(%v, %v) returns %v, want %v",
+				test.head, test.executableOnly, cands, test.wantCandidates)
+			t.Log("returned candidates are:")
+			for _, cand := range cands {
+				t.Logf("%#v", cand)
 			}
 		}
-	})
+	}
 }
 
 func mkdir(dirname string, perm os.FileMode) {

+ 43 - 43
edit/lscolors/feature_test.go

@@ -21,56 +21,56 @@ func TestDetermineFeature(t *testing.T) {
 		}
 	}
 
-	util.InTempDir(func(string) {
+	_, cleanup := util.InTestDir()
+	defer cleanup()
 
-		create("a", 0600)
-		// Regular file.
-		test("a", true, featureRegular)
+	create("a", 0600)
+	// Regular file.
+	test("a", true, featureRegular)
 
-		// Symlink.
-		err := os.Symlink("a", "symlink")
-		if err != nil {
-			t.Logf("Failed to create symlink: %v; skipping symlink test", err)
-		} else {
-			test("symlink", true, featureSymlink)
-		}
+	// Symlink.
+	err := os.Symlink("a", "symlink")
+	if err != nil {
+		t.Logf("Failed to create symlink: %v; skipping symlink test", err)
+	} else {
+		test("symlink", true, featureSymlink)
+	}
 
-		// Broken symlink.
-		err = os.Symlink("aaaa", "bad-symlink")
-		if err != nil {
-			t.Logf("Failed to create bad symlink: %v; skipping bad symlink test", err)
-		} else {
-			test("bad-symlink", true, featureOrphanedSymlink)
-		}
+	// Broken symlink.
+	err = os.Symlink("aaaa", "bad-symlink")
+	if err != nil {
+		t.Logf("Failed to create bad symlink: %v; skipping bad symlink test", err)
+	} else {
+		test("bad-symlink", true, featureOrphanedSymlink)
+	}
 
-		if runtime.GOOS != "windows" {
-			// Multiple hard links.
-			os.Link("a", "a2")
-			test("a", true, featureMultiHardLink)
-		}
+	if runtime.GOOS != "windows" {
+		// Multiple hard links.
+		os.Link("a", "a2")
+		test("a", true, featureMultiHardLink)
+	}
 
-		// Don't test for multiple hard links.
-		test("a", false, featureRegular)
+	// Don't test for multiple hard links.
+	test("a", false, featureRegular)
 
-		// Setuid and Setgid.
-		// XXX(xiaq): Fails.
-		/*
-			create("su", os.ModeSetuid)
-			test("su", true, featureSetuid)
-			create("sg", os.ModeSetgid)
-			test("sg", true, featureSetgid)
-		*/
+	// Setuid and Setgid.
+	// XXX(xiaq): Fails.
+	/*
+		create("su", os.ModeSetuid)
+		test("su", true, featureSetuid)
+		create("sg", os.ModeSetgid)
+		test("sg", true, featureSetgid)
+	*/
 
-		if runtime.GOOS != "windows" {
-			// Executable.
-			create("xu", 0100)
-			create("xg", 0010)
-			create("xo", 0001)
-			test("xu", true, featureExecutable)
-			test("xg", true, featureExecutable)
-			test("xo", true, featureExecutable)
-		}
-	})
+	if runtime.GOOS != "windows" {
+		// Executable.
+		create("xu", 0100)
+		create("xg", 0010)
+		create("xo", 0001)
+		test("xu", true, featureExecutable)
+		test("xg", true, featureExecutable)
+		test("xo", true, featureExecutable)
+	}
 }
 
 func create(fname string, perm os.FileMode) {

+ 12 - 12
eval/builtin_fn_fs_test.go

@@ -8,18 +8,18 @@ import (
 )
 
 func TestBuiltinFnFS(t *testing.T) {
-	pathSep := string(filepath.Separator)
-	InTempHome(func(tmpHome string) {
-		MustMkdirAll("dir", 0700)
-		MustCreateEmpty("file")
+	tmpHome, cleanup := InTempHome()
+	defer cleanup()
 
-		Test(t,
-			That(`path-base a/b/c.png`).Puts("c.png"),
-			That("tilde-abbr "+parse.Quote(filepath.Join(tmpHome, "foobar"))).
-				Puts("~"+pathSep+"foobar"),
+	MustMkdirAll("dir", 0700)
+	MustCreateEmpty("file")
 
-			That(`-is-dir ~/dir`).Puts(true),
-			That(`-is-dir ~/file`).Puts(false),
-		)
-	})
+	Test(t,
+		That(`path-base a/b/c.png`).Puts("c.png"),
+		That("tilde-abbr "+parse.Quote(filepath.Join(tmpHome, "foobar"))).
+			Puts(filepath.Join("~", "foobar")),
+
+		That(`-is-dir ~/dir`).Puts(true),
+		That(`-is-dir ~/file`).Puts(false),
+	)
 }

+ 11 - 10
eval/builtin_fn_misc_test.go

@@ -16,15 +16,16 @@ func TestBuiltinFnMisc(t *testing.T) {
 }
 
 func TestResolve(t *testing.T) {
-	util.InTempDir(func(libdir string) {
-		MustWriteFile("mod.elv", []byte("fn func { }"), 0600)
+	libdir, cleanup := util.InTestDir()
+	defer cleanup()
 
-		TestWithSetup(t, func(ev *Evaler) { ev.SetLibDir(libdir) },
-			That("resolve for").Puts("special"),
-			That("resolve put").Puts("$put~"),
-			That("fn f { }; resolve f").Puts("$f~"),
-			That("use mod; resolve mod:func").Puts("$mod:func~"),
-			That("resolve cat").Puts("(external cat)"),
-		)
-	})
+	MustWriteFile("mod.elv", []byte("fn func { }"), 0600)
+
+	TestWithSetup(t, func(ev *Evaler) { ev.SetLibDir(libdir) },
+		That("resolve for").Puts("special"),
+		That("resolve put").Puts("$put~"),
+		That("fn f { }; resolve f").Puts("$f~"),
+		That("use mod; resolve mod:func").Puts("$mod:func~"),
+		That("resolve cat").Puts("(external cat)"),
+	)
 }

+ 47 - 46
eval/builtin_special_test.go

@@ -63,50 +63,51 @@ func TestBuiltinSpecial(t *testing.T) {
 var useTests = []TestCase{}
 
 func TestUse(t *testing.T) {
-	util.InTempDir(func(libdir string) {
-		MustMkdirAll(filepath.Join("a", "b", "c"), 0700)
-
-		writeMod := func(name, content string) {
-			fname := filepath.Join(strings.Split(name, "/")...) + ".elv"
-			MustWriteFile(fname, []byte(content), 0600)
-		}
-		writeMod("has-init", "put has-init")
-		writeMod("put-x", "put $x")
-		writeMod("lorem", "name = lorem; fn put-name { put $name }")
-		writeMod("d", "name = d")
-		writeMod("a/b/c/d", "name = a/b/c/d")
-		writeMod("a/b/c/x",
-			"use ./d; d = $d:name; use ../../../lorem; lorem = $lorem:name")
-
-		TestWithSetup(t, func(ev *Evaler) { ev.SetLibDir(libdir) },
-			That(`use lorem; put $lorem:name`).Puts("lorem"),
-			// imports are lexically scoped
-			// TODO: Support testing for compilation error
-			// That(`{ use lorem }; put $lorem:name`).ErrorsAny(),
-
-			// use of imported variable is captured in upvalue
-			That(`({ use lorem; put { { put $lorem:name } } })`).Puts("lorem"),
-			// use of imported function is also captured in upvalue
-			That(`{ use lorem; { lorem:put-name } }`).Puts("lorem"),
-
-			// use of a nested module
-			That(`use a/b/c/d; put $d:name`).Puts("a/b/c/d"),
-			// module is cached after first use
-			That(`use has-init; use has-init`).Puts("has-init"),
-			// overriding module
-			That(`use d; put $d:name; use a/b/c/d; put $d:name`).Puts(
-				"d", "a/b/c/d"),
-			// relative uses
-			That(`use a/b/c/x; put $x:d $x:lorem`).Puts("a/b/c/d", "lorem"),
-
-			// Renaming module
-			That(`use a/b/c/d mod; put $mod:name`).Puts("a/b/c/d"),
-
-			// Variables defined in the default global scope is invisible from
-			// modules
-			That("x = foo; use put-x").Errors(),
-
-			// TODO: Test module namespace
-		)
-	})
+	libdir, cleanup := util.InTestDir()
+	defer cleanup()
+
+	MustMkdirAll(filepath.Join("a", "b", "c"), 0700)
+
+	writeMod := func(name, content string) {
+		fname := filepath.Join(strings.Split(name, "/")...) + ".elv"
+		MustWriteFile(fname, []byte(content), 0600)
+	}
+	writeMod("has-init", "put has-init")
+	writeMod("put-x", "put $x")
+	writeMod("lorem", "name = lorem; fn put-name { put $name }")
+	writeMod("d", "name = d")
+	writeMod("a/b/c/d", "name = a/b/c/d")
+	writeMod("a/b/c/x",
+		"use ./d; d = $d:name; use ../../../lorem; lorem = $lorem:name")
+
+	TestWithSetup(t, func(ev *Evaler) { ev.SetLibDir(libdir) },
+		That(`use lorem; put $lorem:name`).Puts("lorem"),
+		// imports are lexically scoped
+		// TODO: Support testing for compilation error
+		// That(`{ use lorem }; put $lorem:name`).ErrorsAny(),
+
+		// use of imported variable is captured in upvalue
+		That(`({ use lorem; put { { put $lorem:name } } })`).Puts("lorem"),
+		// use of imported function is also captured in upvalue
+		That(`{ use lorem; { lorem:put-name } }`).Puts("lorem"),
+
+		// use of a nested module
+		That(`use a/b/c/d; put $d:name`).Puts("a/b/c/d"),
+		// module is cached after first use
+		That(`use has-init; use has-init`).Puts("has-init"),
+		// overriding module
+		That(`use d; put $d:name; use a/b/c/d; put $d:name`).Puts(
+			"d", "a/b/c/d"),
+		// relative uses
+		That(`use a/b/c/x; put $x:d $x:lorem`).Puts("a/b/c/d", "lorem"),
+
+		// Renaming module
+		That(`use a/b/c/d mod; put $mod:name`).Puts("a/b/c/d"),
+
+		// Variables defined in the default global scope is invisible from
+		// modules
+		That("x = foo; use put-x").Errors(),
+
+		// TODO: Test module namespace
+	)
 }

+ 45 - 39
eval/chdir_test.go

@@ -15,61 +15,67 @@ func (t testAddDirer) AddDir(dir string, weight float64) error {
 }
 
 func TestChdir(t *testing.T) {
-	inWithTempDir(func(pwd, dst string) {
-		ev := NewEvaler()
+	_, dst, cleanup := inWithTestDir()
+	defer cleanup()
 
-		argDirInBefore, argDirInAfter := "", ""
-		ev.AddBeforeChdir(func(dir string) { argDirInBefore = dir })
-		ev.AddAfterChdir(func(dir string) { argDirInAfter = dir })
+	ev := NewEvaler()
 
-		err := ev.Chdir(dst)
+	argDirInBefore, argDirInAfter := "", ""
+	ev.AddBeforeChdir(func(dir string) { argDirInBefore = dir })
+	ev.AddAfterChdir(func(dir string) { argDirInAfter = dir })
 
-		if err != nil {
-			t.Errorf("Chdir => error %v", err)
-		}
-		if envPwd := os.Getenv("PWD"); envPwd != dst {
-			t.Errorf("$PWD is %q after Chdir, want %q", envPwd, dst)
-		}
+	err := ev.Chdir(dst)
 
-		if argDirInBefore != dst {
-			t.Errorf("Chdir called before-hook with %q, want %q",
-				argDirInBefore, dst)
-		}
-		if argDirInAfter != dst {
-			t.Errorf("Chdir called before-hook with %q, want %q",
-				argDirInAfter, dst)
-		}
-	})
+	if err != nil {
+		t.Errorf("Chdir => error %v", err)
+	}
+	if envPwd := os.Getenv("PWD"); envPwd != dst {
+		t.Errorf("$PWD is %q after Chdir, want %q", envPwd, dst)
+	}
+
+	if argDirInBefore != dst {
+		t.Errorf("Chdir called before-hook with %q, want %q",
+			argDirInBefore, dst)
+	}
+	if argDirInAfter != dst {
+		t.Errorf("Chdir called before-hook with %q, want %q",
+			argDirInAfter, dst)
+	}
 }
 
 func TestChdirElvishHooks(t *testing.T) {
-	inWithTempDir(func(pwd, dst string) {
-		Test(t,
-			That(`
+	_, dst, cleanup := inWithTestDir()
+	defer cleanup()
+
+	Test(t,
+		That(`
 			dir-in-before dir-in-after = '' ''
 			@before-chdir = [dst]{ dir-in-before = $dst }
 			@after-chdir  = [dst]{ dir-in-after  = $dst }
 			cd `+parse.Quote(dst)+`
 			put $dir-in-before $dir-in-after
 			`).Puts(dst, dst),
-		)
-	})
+	)
 }
 
 func TestChdirError(t *testing.T) {
-	util.InTempDir(func(pwd string) {
-		ev := NewEvaler()
-		err := ev.Chdir("i/dont/exist")
-		if err == nil {
-			t.Errorf("Chdir => no error when dir does not exist")
-		}
-	})
+	_, cleanup := util.InTestDir()
+	defer cleanup()
+
+	ev := NewEvaler()
+	err := ev.Chdir("i/dont/exist")
+	if err == nil {
+		t.Errorf("Chdir => no error when dir does not exist")
+	}
 }
 
-func inWithTempDir(f func(pwd, other string)) {
-	util.InTempDir(func(pwd string) {
-		util.WithTempDir(func(other string) {
-			f(pwd, other)
-		})
-	})
+// Creates two test directories, changing into the first one. Returns those two
+// directories and a function to restore the original state.
+func inWithTestDir() (pwd, other string, cleanup func()) {
+	pwd, cleanup1 := util.InTestDir()
+	other, cleanup2 := util.TestDir()
+	return pwd, other, func() {
+		cleanup2()
+		cleanup1()
+	}
 }

+ 23 - 22
eval/compile_value_test.go

@@ -135,27 +135,28 @@ func getFilesBut(excludes ...string) []string {
 }
 
 func TestWildcard(t *testing.T) {
-	util.InTempDir(func(string) {
-		for _, filename := range filesToCreate {
-			MustCreateEmpty(filename)
-		}
-		for _, dirname := range dirsToCreate {
-			MustMkdirAll(dirname, 0700)
-		}
+	_, cleanup := util.InTestDir()
+	defer cleanup()
 
-		Test(t,
-			That("put *").PutsStrings(fileListing),
-			That("put a/b/nonexistent*").ErrorsWith(ErrWildcardNoMatch),
-			That("put a/b/nonexistent*[nomatch-ok]").DoesNothing(),
-
-			// Character set and range
-			That("put ?[set:ab]*").PutsStrings(getFilesWithPrefix("a", "b")),
-			That("put ?[range:a-c]*").PutsStrings(getFilesWithPrefix("a", "b", "c")),
-			That("put ?[range:a~c]*").PutsStrings(getFilesWithPrefix("a", "b")),
-			That("put *[range:a-z]").Puts("bar", "dir", "foo", "ipsum", "lorem"),
-
-			// Exclusion
-			That("put *[but:foo][but:lorem]").PutsStrings(getFilesBut("foo", "lorem")),
-		)
-	})
+	for _, filename := range filesToCreate {
+		MustCreateEmpty(filename)
+	}
+	for _, dirname := range dirsToCreate {
+		MustMkdirAll(dirname, 0700)
+	}
+
+	Test(t,
+		That("put *").PutsStrings(fileListing),
+		That("put a/b/nonexistent*").ErrorsWith(ErrWildcardNoMatch),
+		That("put a/b/nonexistent*[nomatch-ok]").DoesNothing(),
+
+		// Character set and range
+		That("put ?[set:ab]*").PutsStrings(getFilesWithPrefix("a", "b")),
+		That("put ?[range:a-c]*").PutsStrings(getFilesWithPrefix("a", "b", "c")),
+		That("put ?[range:a~c]*").PutsStrings(getFilesWithPrefix("a", "b")),
+		That("put *[range:a-z]").Puts("bar", "dir", "foo", "ipsum", "lorem"),
+
+		// Exclusion
+		That("put *[but:foo][but:lorem]").PutsStrings(getFilesBut("foo", "lorem")),
+	)
 }

+ 10 - 8
eval/testutils.go

@@ -254,13 +254,15 @@ func MustWriteFile(filename string, data []byte, perm os.FileMode) {
 	}
 }
 
-// InTempHome is like util.InTempDir, but it also sets HOME to the temporary
-// directory when f is called.
-func InTempHome(f func(string)) {
-	util.InTempDir(func(tmpHome string) {
-		oldHome := os.Getenv("HOME")
-		os.Setenv("HOME", tmpHome)
-		f(tmpHome)
+// InTempHome is like util.InTestDir, but it also sets HOME to the temporary
+// directory and restores the original HOME in cleanup.
+func InTempHome() (string, func()) {
+	oldHome := os.Getenv("HOME")
+	tmpHome, cleanup := util.InTestDir()
+	os.Setenv("HOME", tmpHome)
+
+	return tmpHome, func() {
 		os.Setenv("HOME", oldHome)
-	})
+		cleanup()
+	}
 }

+ 25 - 24
glob/glob_test.go

@@ -69,31 +69,32 @@ func init() {
 }
 
 func TestGlob(t *testing.T) {
-	util.InTempDir(func(string) {
-		for _, dir := range append(mkdirs, mkdirDots...) {
-			err := os.Mkdir(dir, 0755)
-			if err != nil {
-				panic(err)
-			}
+	_, cleanup := util.InTestDir()
+	defer cleanup()
+
+	for _, dir := range append(mkdirs, mkdirDots...) {
+		err := os.Mkdir(dir, 0755)
+		if err != nil {
+			panic(err)
 		}
-		for _, file := range append(creates, createDots...) {
-			f, err := os.Create(file)
-			if err != nil {
-				panic(err)
-			}
-			f.Close()
+	}
+	for _, file := range append(creates, createDots...) {
+		f, err := os.Create(file)
+		if err != nil {
+			panic(err)
 		}
-		for _, tc := range globCases {
-			names := []string{}
-			Glob(tc.pattern, func(name string) bool {
-				names = append(names, name)
-				return true
-			})
-			sort.Strings(names)
-			sort.Strings(tc.want)
-			if !reflect.DeepEqual(names, tc.want) {
-				t.Errorf(`Glob(%q, "") => %v, want %v`, tc.pattern, names, tc.want)
-			}
+		f.Close()
+	}
+	for _, tc := range globCases {
+		names := []string{}
+		Glob(tc.pattern, func(name string) bool {
+			names = append(names, name)
+			return true
+		})
+		sort.Strings(names)
+		sort.Strings(tc.want)
+		if !reflect.DeepEqual(names, tc.want) {
+			t.Errorf(`Glob(%q, "") => %v, want %v`, tc.pattern, names, tc.want)
 		}
-	})
+	}
 }

+ 14 - 13
util/claim_test.go

@@ -14,21 +14,22 @@ var claimFileTests = []struct {
 }
 
 func TestClaimFile(t *testing.T) {
-	InTempDir(func(tmpdir string) {
-		touch("a0.log")
-		touch("a1.log")
-		touch("a8.log")
+	_, cleanup := InTestDir()
+	defer cleanup()
 
-		for _, test := range claimFileTests {
-			f, err := ClaimFile(".", test.pattern)
-			if err != nil {
-				t.Errorf("ClaimFile errors: %v", err)
-			}
-			if f.Name() != test.wantFileName {
-				t.Errorf("ClaimFile claims %s, want %s", f.Name(), test.wantFileName)
-			}
+	touch("a0.log")
+	touch("a1.log")
+	touch("a8.log")
+
+	for _, test := range claimFileTests {
+		f, err := ClaimFile(".", test.pattern)
+		if err != nil {
+			t.Errorf("ClaimFile errors: %v", err)
+		}
+		if f.Name() != test.wantFileName {
+			t.Errorf("ClaimFile claims %s, want %s", f.Name(), test.wantFileName)
 		}
-	})
+	}
 }
 
 func touch(fname string) {

+ 32 - 31
util/getwd_test.go

@@ -9,40 +9,41 @@ import (
 )
 
 func TestGetwd(t *testing.T) {
-	InTempDir(func(tmpdir string) {
-		// On some systems /tmp is a symlink.
-		tmpdir, err := filepath.EvalSymlinks(tmpdir)
-		if err != nil {
-			panic(err)
-		}
-		// Override $HOME to make sure that tmpdir is not abbreviatable.
-		os.Setenv("HOME", "/does/not/exist")
-		if gotwd := Getwd(); gotwd != tmpdir {
-			t.Errorf("Getwd() -> %v, want %v", gotwd, tmpdir)
-		}
+	tmpdir, cleanup := InTestDir()
+	defer cleanup()
 
-		// Override $HOME to trick GetHome.
-		os.Setenv("HOME", tmpdir)
+	// On some systems /tmp is a symlink.
+	tmpdir, err := filepath.EvalSymlinks(tmpdir)
+	if err != nil {
+		panic(err)
+	}
+	// Override $HOME to make sure that tmpdir is not abbreviatable.
+	os.Setenv("HOME", "/does/not/exist")
+	if gotwd := Getwd(); gotwd != tmpdir {
+		t.Errorf("Getwd() -> %v, want %v", gotwd, tmpdir)
+	}
 
-		if gotwd := Getwd(); gotwd != "~" {
-			t.Errorf("Getwd() -> %v, want ~", gotwd)
-		}
+	// Override $HOME to trick GetHome.
+	os.Setenv("HOME", tmpdir)
 
-		mustOK(os.Mkdir("a", 0700))
-		mustOK(os.Chdir("a"))
-		if gotwd := Getwd(); gotwd != filepath.Join("~", "a") {
-			t.Errorf("Getwd() -> %v, want ~/a", gotwd)
-		}
+	if gotwd := Getwd(); gotwd != "~" {
+		t.Errorf("Getwd() -> %v, want ~", gotwd)
+	}
+
+	mustOK(os.Mkdir("a", 0700))
+	mustOK(os.Chdir("a"))
+	if gotwd := Getwd(); gotwd != filepath.Join("~", "a") {
+		t.Errorf("Getwd() -> %v, want ~/a", gotwd)
+	}
 
-		// On macOS os.Getwd will still return the old path name in face of
-		// directory being removed. Hence we only test this on Linux.
-		// TODO(xiaq): Check the behavior on other BSDs and relax this condition
-		// if possible.
-		if runtime.GOOS == "linux" {
-			mustOK(os.Remove(path.Join(tmpdir, "a")))
-			if gotwd := Getwd(); gotwd != "?" {
-				t.Errorf("Getwd() -> %v, want ?", gotwd)
-			}
+	// On macOS os.Getwd will still return the old path name in face of
+	// directory being removed. Hence we only test this on Linux.
+	// TODO(xiaq): Check the behavior on other BSDs and relax this condition
+	// if possible.
+	if runtime.GOOS == "linux" {
+		mustOK(os.Remove(path.Join(tmpdir, "a")))
+		if gotwd := Getwd(); gotwd != "?" {
+			t.Errorf("Getwd() -> %v, want ?", gotwd)
 		}
-	})
+	}
 }

+ 8 - 57
util/tempdir.go

@@ -1,52 +1,9 @@
 package util
 
 import (
-	"fmt"
-	"io/ioutil"
 	"os"
-	"path/filepath"
 )
 
-// WithTempDirs creates a requested number of temporary directories and runs a
-// function, passing the paths of the temporary directories; the passed paths
-// all have their symlinks resolved using filepath.EvalSymlinks. After the
-// function returns, it removes the temporary directories. It panics if it
-// cannot make a temporary directory, and prints an error message to stderr if
-// it cannot remove the temporary directories.
-//
-// It is useful in tests.
-func WithTempDirs(n int, f func([]string)) {
-	tmpdirs := make([]string, n)
-	for i := range tmpdirs {
-		tmpdir, err := ioutil.TempDir("", "elvishtest.")
-		if err != nil {
-			panic(err)
-		}
-		tmpdirs[i], err = filepath.EvalSymlinks(tmpdir)
-		if err != nil {
-			panic(err)
-		}
-	}
-	defer func() {
-		for _, tmpdir := range tmpdirs {
-			err := os.RemoveAll(tmpdir)
-			if err != nil {
-				fmt.Fprintln(os.Stderr, "Warning: failed to remove temp dir", tmpdir)
-			}
-		}
-	}()
-
-	f(tmpdirs)
-}
-
-// WithTempDir is like with WithTempDirs, except that it always creates one
-// temporary directory and pass the function a string instead of []string.
-func WithTempDir(f func(string)) {
-	WithTempDirs(1, func(s []string) {
-		f(s[0])
-	})
-}
-
 // InTempDir is like WithTempDir, but also cd into the directory before running
 // the function, and cd backs after running the function if possible.
 //
@@ -54,22 +11,16 @@ func WithTempDir(f func(string)) {
 //
 // It is useful in tests.
 func InTempDir(f func(string)) {
-	WithTempDir(func(tmpdir string) {
-		oldpwd, err := os.Getwd()
-		if err != nil {
-			panic(err)
-		}
-
-		mustChdir(tmpdir)
-		defer mustChdir(oldpwd)
-
-		f(tmpdir)
-	})
-}
+	tmpdir, cleanup := TestDir()
+	defer cleanup()
 
-func mustChdir(dir string) {
-	err := os.Chdir(dir)
+	oldpwd, err := os.Getwd()
 	if err != nil {
 		panic(err)
 	}
+
+	mustChdir(tmpdir)
+	defer mustChdir(oldpwd)
+
+	f(tmpdir)
 }

+ 0 - 66
util/tempdir_test.go

@@ -1,66 +0,0 @@
-package util
-
-import (
-	"os"
-	"path/filepath"
-	"testing"
-)
-
-func TestWithTempDirs_PassesDirs(t *testing.T) {
-	WithTempDirs(10, func(dirs []string) {
-		for _, dir := range dirs {
-			stat, err := os.Stat(dir)
-			if err != nil {
-				t.Errorf("WithTempDir passes %q, but it cannot be stat'ed", dir)
-			}
-			if !stat.IsDir() {
-				t.Errorf("WithTempDir passes %q, but it is not dir", dir)
-			}
-		}
-	})
-}
-
-func TestWithTempDir_RemovesDirs(t *testing.T) {
-	var tempDirs []string
-	WithTempDirs(10, func(dirs []string) { tempDirs = dirs })
-	for _, dir := range tempDirs {
-		_, err := os.Stat(dir)
-		if err == nil {
-			t.Errorf("After WithTempDir returns, %q still exists", dir)
-		}
-	}
-}
-
-func TestInTempDir_CDIn(t *testing.T) {
-	InTempDir(func(tmpDir string) {
-		pwd := getPwd()
-		evaledTmpDir, err := filepath.EvalSymlinks(tmpDir)
-		if err != nil {
-			panic(err)
-		}
-		if pwd != evaledTmpDir {
-			t.Errorf("In InTempDir, working dir (%q) != EvalSymlinks(argument) (%q)", pwd, evaledTmpDir)
-		}
-	})
-}
-
-func TestInTempDir_CDOut(t *testing.T) {
-	before := getPwd()
-	InTempDir(func(tmpDir string) {})
-	after := getPwd()
-	if before != after {
-		t.Errorf("With InTempDir, working dir before %q != after %q", before, after)
-	}
-}
-
-func getPwd() string {
-	dir, err := os.Getwd()
-	if err != nil {
-		panic(err)
-	}
-	dir, err = filepath.EvalSymlinks(dir)
-	if err != nil {
-		panic(err)
-	}
-	return dir
-}

+ 56 - 0
util/testdir.go

@@ -0,0 +1,56 @@
+package util
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+)
+
+// TestDir creates a temporary directory for testing. It returns the path of the
+// temporary directory and a cleanup function to remove the temporary directory.
+// The path has symlinks resolved with filepath.EvalSymlinks.
+//
+// It panics if the test directory cannot be created or symlinks cannot be
+// resolved. It is only suitable for use in tests.
+func TestDir() (string, func()) {
+	dir, err := ioutil.TempDir("", "elvishtest.")
+	if err != nil {
+		panic(err)
+	}
+	dir, err = filepath.EvalSymlinks(dir)
+	if err != nil {
+		panic(err)
+	}
+	return dir, func() {
+		err := os.RemoveAll(dir)
+		if err != nil {
+			fmt.Fprintln(os.Stderr, "Warning: failed to remove temp dir", dir)
+		}
+	}
+}
+
+// InTestDir is like TestDir, but also changes into the test directory, and the
+// cleanup function also changes back to the original working directory.
+//
+// It panics if it could not get the working directory or change directory. It
+// is only suitable for use in tests.
+func InTestDir() (string, func()) {
+	oldWd, err := os.Getwd()
+	if err != nil {
+		panic(err)
+	}
+	dir, cleanup := TestDir()
+	mustChdir(dir)
+	return dir, func() {
+		mustChdir(oldWd)
+		cleanup()
+	}
+}
+
+func mustChdir(dir string) {
+	err := os.Chdir(dir)
+	if err != nil {
+		panic(err)
+	}
+}

+ 82 - 0
util/testdir_test.go

@@ -0,0 +1,82 @@
+package util
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"testing"
+)
+
+func TestTestDir_DirIsValid(t *testing.T) {
+	dir, cleanup := TestDir()
+	defer cleanup()
+
+	stat, err := os.Stat(dir)
+	if err != nil {
+		t.Errorf("TestDir returns %q which cannot be stated", dir)
+	}
+	if !stat.IsDir() {
+		t.Errorf("TestDir returns %q which is not a dir", dir)
+	}
+}
+
+func TestTestDir_DirHasSymlinksResolved(t *testing.T) {
+	dir, cleanup := TestDir()
+	defer cleanup()
+
+	resolved, err := filepath.EvalSymlinks(dir)
+	if err != nil {
+		panic(err)
+	}
+	if dir != resolved {
+		t.Errorf("TestDir returns %q, but it resolves to %q", dir, resolved)
+	}
+}
+
+func TestTestDir_CleanupRemovesDirRecursively(t *testing.T) {
+	dir, cleanup := TestDir()
+
+	err := ioutil.WriteFile(filepath.Join(dir, "a"), []byte("test"), 0600)
+	if err != nil {
+		panic(err)
+	}
+
+	cleanup()
+	if _, err := os.Stat(dir); err == nil {
+		t.Errorf("Dir %q still exists after cleanup", dir)
+	}
+}
+
+func TestInTestDir_ChangesIntoTempDir(t *testing.T) {
+	dir, cleanup := InTestDir()
+	defer cleanup()
+
+	pwd := getWd()
+	if dir != pwd {
+		t.Errorf("InTestDir returns %q but pwd is %q", dir, pwd)
+	}
+}
+
+func TestInTestDir_CleanupChangesBackToOldWd(t *testing.T) {
+	before := getWd()
+
+	_, cleanup := InTestDir()
+	cleanup()
+
+	after := getWd()
+	if before != after {
+		t.Errorf("PWD is %q before InTestDir, but %q after cleanup", before, after)
+	}
+}
+
+func getWd() string {
+	dir, err := os.Getwd()
+	if err != nil {
+		panic(err)
+	}
+	dir, err = filepath.EvalSymlinks(dir)
+	if err != nil {
+		panic(err)
+	}
+	return dir
+}