Split off manifest and multi FS tests into separate file.

Change-Id: I02a1325d5fc3a5340f1f4c1d6877ac0f2f8e9101
diff --git a/fs/gitilesfs_test.go b/fs/gitilesfs_test.go
index 797358d..ffb0961 100644
--- a/fs/gitilesfs_test.go
+++ b/fs/gitilesfs_test.go
@@ -24,20 +24,15 @@
 	"os"
 	"os/exec"
 	"path/filepath"
-	"reflect"
 	"regexp"
-	"sort"
 	"strings"
 	"sync"
 	"syscall"
 	"testing"
 	"time"
 
-	"github.com/google/slothfs/cache"
 	"github.com/google/slothfs/gitiles"
 	"github.com/google/slothfs/manifest"
-	"github.com/hanwen/go-fuse/fuse"
-	"github.com/hanwen/go-fuse/fuse/nodefs"
 )
 
 const fuseDebug = false
@@ -373,7 +368,7 @@
 	}
 }
 
-func TestGitilesFS(t *testing.T) {
+func TestGitilesFSBasic(t *testing.T) {
 	fix, err := newTestFixture()
 	if err != nil {
 		t.Fatal("newTestFixture", err)
@@ -508,360 +503,3 @@
 		}
 	}
 }
-
-func newManifestTestFixture(mf *manifest.Manifest) (*testFixture, error) {
-	fix, err := newTestFixture()
-	if err != nil {
-		return nil, err
-	}
-
-	opts := ManifestOptions{
-		Manifest: mf,
-	}
-
-	fs, err := NewManifestFS(fix.service, fix.cache, opts)
-	if err != nil {
-		return nil, err
-	}
-	if err := fix.mount(fs); err != nil {
-		return nil, err
-	}
-
-	return fix, nil
-}
-
-func TestManifestFSCloneOption(t *testing.T) {
-	mf := *testManifest
-	for i := range mf.Project {
-		mf.Project[i].CloneDepth = "1"
-	}
-
-	fix, err := newManifestTestFixture(&mf)
-	if err != nil {
-		t.Fatalf("newManifestTestFixture: %v", err)
-	}
-	defer fix.cleanup()
-
-	fs := fix.root.(*manifestFSRoot)
-	ch := fs.Inode()
-	for _, n := range []string{"build", "kati", "AUTHORS"} {
-		newCh := ch.GetChild(n)
-		if ch == nil {
-			t.Fatalf("node for %q not found. Have %s", n, ch.Children())
-		}
-		ch = newCh
-	}
-
-	giNode, ok := ch.Node().(*gitilesNode)
-	if !ok {
-		t.Fatalf("got node type %T, want *gitilesNode", ch.Node())
-	}
-
-	if giNode.clone {
-		t.Errorf("file had clone set.")
-	}
-}
-
-func TestManifestFSTimestamps(t *testing.T) {
-	fix, err := newManifestTestFixture(testManifest)
-	if err != nil {
-		t.Fatal("newTestFixture", err)
-	}
-	defer fix.cleanup()
-
-	var zeroFiles []string
-	if err := filepath.Walk(fix.mntDir, func(n string, fi os.FileInfo, err error) error {
-		if fi != nil && fi.ModTime().UnixNano() == 0 {
-			r, _ := filepath.Rel(fix.mntDir, n)
-			zeroFiles = append(zeroFiles, r)
-		}
-		return nil
-	}); err != nil {
-		t.Fatalf("Walk: %v", err)
-	}
-	if len(zeroFiles) > 0 {
-		sort.Strings(zeroFiles)
-		t.Errorf("found files with zero timestamps: %v", zeroFiles)
-	}
-}
-
-func TestManifestFSBasic(t *testing.T) {
-	fix, err := newManifestTestFixture(testManifest)
-	if err != nil {
-		t.Fatal("newTestFixture", err)
-	}
-	defer fix.cleanup()
-
-	fn := filepath.Join(fix.mntDir, "build", "kati", "AUTHORS")
-	fi, err := os.Lstat(fn)
-	if err != nil {
-		t.Fatalf("Lstat(%s): %v", fn, err)
-	}
-	if fi.Size() != 373 {
-		t.Errorf("got size %d want %d", fi.Size(), 373)
-	}
-
-	contents, err := ioutil.ReadFile(fn)
-	if err != nil {
-		t.Fatalf("ReadFile(%s): %v", fn, err)
-	}
-
-	want := testGitiles["/platform/build/kati/+show/ce34badf691d36e8048b63f89d1a86ee5fa4325c/AUTHORS?format=TEXT"]
-	if string(contents) != want {
-		t.Fatalf("got %q, want %q", contents, want)
-	}
-
-	copyPath := filepath.Join(fix.mntDir, "build", "copydest")
-	if copyFI, err := os.Lstat(copyPath); err != nil {
-		t.Errorf("Lstat(%s): %v", copyPath, err)
-	} else {
-		copyStat := copyFI.Sys().(*syscall.Stat_t)
-		origStat := fi.Sys().(*syscall.Stat_t)
-
-		if !reflect.DeepEqual(copyStat, origStat) {
-			t.Errorf("got stat %v, want %v", copyStat, origStat)
-		}
-	}
-
-	linkPath := filepath.Join(fix.mntDir, "build", "linkdest")
-	if got, err := os.Readlink(linkPath); err != nil {
-		t.Errorf("Readlink(%s): %v", linkPath, err)
-	} else if want := "kati/AUTHORS"; got != want {
-		t.Errorf("Readlink(%s) = %q, want %q", linkPath, got, want)
-	}
-}
-
-func TestManifestFSXMLFile(t *testing.T) {
-	fix, err := newManifestTestFixture(testManifest)
-	if err != nil {
-		t.Fatal("newTestFixture", err)
-	}
-	defer fix.cleanup()
-
-	xmlPath := filepath.Join(fix.mntDir, ".slothfs", "manifest.xml")
-	fuseMF, err := manifest.ParseFile(xmlPath)
-	if err != nil {
-		t.Fatalf("ParseFile(%s): %v", xmlPath, err)
-	}
-
-	if !reflect.DeepEqual(fuseMF, testManifest) {
-		t.Errorf("read back manifest %v, want %v", fuseMF, testManifest)
-	}
-}
-
-type testFixture struct {
-	dir        string
-	mntDir     string
-	server     *fuse.Server
-	cache      *cache.Cache
-	testServer *testServer
-	service    *gitiles.Service
-	root       nodefs.Node
-}
-
-func (f *testFixture) cleanup() {
-	if f.testServer != nil {
-		f.testServer.listener.Close()
-	}
-	if f.server != nil {
-		f.server.Unmount()
-	}
-	os.RemoveAll(f.dir)
-}
-
-func newTestFixture() (*testFixture, error) {
-	d, err := ioutil.TempDir("", "multifstest")
-	if err != nil {
-		return nil, err
-	}
-
-	fixture := &testFixture{dir: d}
-
-	fixture.cache, err = cache.NewCache(filepath.Join(d, "/cache"), cache.Options{})
-	if err != nil {
-		return nil, err
-	}
-
-	fixture.testServer, err = newTestServer()
-	if err != nil {
-		return nil, err
-	}
-
-	fixture.service, err = gitiles.NewService(gitiles.Options{
-		Address: fmt.Sprintf("http://%s", fixture.testServer.addr),
-	})
-	if err != nil {
-		return nil, err
-	}
-
-	return fixture, nil
-}
-
-func (f *testFixture) mount(root nodefs.Node) error {
-	f.mntDir = filepath.Join(f.dir, "mnt")
-	if err := os.Mkdir(f.mntDir, 0755); err != nil {
-		return err
-	}
-
-	fuseOpts := &nodefs.Options{
-		EntryTimeout:    time.Hour,
-		NegativeTimeout: time.Hour,
-		AttrTimeout:     time.Hour,
-	}
-
-	var err error
-	f.server, _, err = nodefs.MountRoot(f.mntDir, root, fuseOpts)
-	if err != nil {
-		return err
-	}
-
-	if fuseDebug {
-		f.server.SetDebug(true)
-	}
-	go f.server.Serve()
-
-	f.root = root
-	return nil
-}
-
-func TestMultiFSBrokenXML(t *testing.T) {
-	fix, err := newTestFixture()
-	if err != nil {
-		t.Fatalf("newTestFixture: %v", err)
-	}
-	defer fix.cleanup()
-
-	brokenXMLFile := filepath.Join(fix.dir, "broken.xml")
-	if err := ioutil.WriteFile(brokenXMLFile, []byte("I'm not XML."), 0644); err != nil {
-		t.Errorf("WriteFile(%s): %v", brokenXMLFile, err)
-	}
-
-	opts := MultiFSOptions{}
-	fs := NewMultiFS(fix.service, fix.cache, opts)
-
-	if err := fix.mount(fs); err != nil {
-		t.Fatalf("mount: %v", err)
-	}
-
-	if err := os.Symlink(brokenXMLFile, filepath.Join(fix.mntDir, "config", "ws")); err == nil {
-		t.Fatalf("want error for broken XML file")
-	}
-}
-
-func TestMultiFSBasic(t *testing.T) {
-	fix, err := newTestFixture()
-	if err != nil {
-		t.Fatalf("newTestFixture: %v", err)
-	}
-	defer fix.cleanup()
-
-	xmlFile := filepath.Join(fix.dir, "manifest.xml")
-	if err := ioutil.WriteFile(xmlFile, []byte(testManifestXML), 0644); err != nil {
-		t.Errorf("WriteFile(%s): %v", xmlFile, err)
-	}
-
-	opts := MultiFSOptions{}
-	fs := NewMultiFS(fix.service, fix.cache, opts)
-
-	if err := fix.mount(fs); err != nil {
-		t.Fatalf("mount: %v", err)
-	}
-
-	wsDir := filepath.Join(fix.mntDir, "ws")
-	if fi, err := os.Lstat(wsDir); err == nil {
-		t.Fatalf("got %v, want non-existent workspace dir", fi)
-	}
-
-	configName := filepath.Join(fix.mntDir, "config", "ws")
-	if err := os.Symlink(xmlFile, configName); err != nil {
-		t.Fatalf("Symlink(%s):  %v", xmlFile, err)
-	}
-
-	if _, err := os.Lstat(wsDir); err != nil {
-		t.Fatalf("Lstat(%s): %v", wsDir, err)
-	}
-
-	if got, err := os.Readlink(configName); err != nil {
-		t.Fatalf("Readlink(%s): %v", configName, err)
-	} else if want := "../ws/.slothfs/manifest.xml"; got != want {
-		t.Errorf("got link %s, want %s", got, want)
-	}
-
-	if _, err := manifest.ParseFile(configName); err != nil {
-		t.Fatalf("ParseFile(%s): %v", configName, err)
-	}
-
-	fn := filepath.Join(wsDir, "build", "kati", "AUTHORS")
-	if fi, err := os.Lstat(fn); err != nil {
-		t.Fatalf("Lstat(%s): %v", fn, err)
-	} else if fi.Size() != 373 {
-		t.Errorf("got %d, want size 373", fi.Size())
-	}
-
-	if err := os.Remove(configName); err != nil {
-		t.Fatalf("Delete(%s): %v", configName, err)
-	}
-
-	if fi, err := os.Lstat(wsDir); err == nil {
-		t.Errorf("Lstat(%s): got %v, want error", wsDir, fi)
-	}
-}
-
-func TestMultiFSManifestDir(t *testing.T) {
-	fix, err := newTestFixture()
-	if err != nil {
-		t.Fatalf("newTestFixture: %v", err)
-	}
-	defer fix.cleanup()
-
-	mfDir := filepath.Join(fix.dir, "manifests")
-	if err := os.MkdirAll(mfDir, 0755); err != nil {
-		t.Fatalf("MkdirAll: %v", err)
-	}
-
-	xmlFile := filepath.Join(mfDir, "ws")
-	if err := ioutil.WriteFile(xmlFile, []byte(testManifestXML), 0644); err != nil {
-		t.Errorf("WriteFile(%s): %v", xmlFile, err)
-	}
-
-	opts := MultiFSOptions{
-		ManifestDir: mfDir,
-	}
-	fs := NewMultiFS(fix.service, fix.cache, opts)
-
-	if err := fix.mount(fs); err != nil {
-		t.Fatalf("mount: %v", err)
-	}
-
-	wsDir := filepath.Join(fix.mntDir, "ws")
-	if _, err := os.Lstat(wsDir); err != nil {
-		t.Fatalf("Lstat(%s): %v", wsDir, err)
-	}
-
-	if err := os.Remove(filepath.Join(fix.mntDir, "config", "ws")); err != nil {
-		t.Fatalf("Remove(config link): %v", err)
-	}
-
-	if fi, err := os.Lstat(filepath.Join(mfDir, "ws")); err == nil {
-		t.Errorf("'ws' still in manifest dir: %v", fi)
-	}
-
-	f, err := ioutil.TempFile("", "")
-	if err != nil {
-		t.Fatalf("TempFile: %v", err)
-	}
-	if err := ioutil.WriteFile(f.Name(), []byte(testManifestXML), 0644); err != nil {
-		t.Errorf("WriteFile(%s): %v", xmlFile, err)
-	}
-
-	configName := filepath.Join(fix.mntDir, "config", "ws2")
-	if err := os.Symlink(f.Name(), configName); err != nil {
-		t.Fatalf("Symlink(%s):  %v", xmlFile, err)
-	}
-
-	// XML file appears again.
-	xmlFile = filepath.Join(mfDir, "ws2")
-	if _, err := os.Stat(xmlFile); err != nil {
-		t.Errorf("Stat(%s): %v", xmlFile, err)
-	}
-}
diff --git a/fs/manifestfs_test.go b/fs/manifestfs_test.go
new file mode 100644
index 0000000..05c1ca9
--- /dev/null
+++ b/fs/manifestfs_test.go
@@ -0,0 +1,390 @@
+// Copyright 2016 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package fs
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"reflect"
+	"sort"
+	"syscall"
+	"testing"
+	"time"
+
+	"github.com/google/slothfs/cache"
+	"github.com/google/slothfs/gitiles"
+	"github.com/google/slothfs/manifest"
+	"github.com/hanwen/go-fuse/fuse"
+	"github.com/hanwen/go-fuse/fuse/nodefs"
+)
+
+func newManifestTestFixture(mf *manifest.Manifest) (*testFixture, error) {
+	fix, err := newTestFixture()
+	if err != nil {
+		return nil, err
+	}
+
+	opts := ManifestOptions{
+		Manifest: mf,
+	}
+
+	fs, err := NewManifestFS(fix.service, fix.cache, opts)
+	if err != nil {
+		return nil, err
+	}
+	if err := fix.mount(fs); err != nil {
+		return nil, err
+	}
+
+	return fix, nil
+}
+
+func TestManifestFSCloneOption(t *testing.T) {
+	mf := *testManifest
+	for i := range mf.Project {
+		mf.Project[i].CloneDepth = "1"
+	}
+
+	fix, err := newManifestTestFixture(&mf)
+	if err != nil {
+		t.Fatalf("newManifestTestFixture: %v", err)
+	}
+	defer fix.cleanup()
+
+	fs := fix.root.(*manifestFSRoot)
+	ch := fs.Inode()
+	for _, n := range []string{"build", "kati", "AUTHORS"} {
+		newCh := ch.GetChild(n)
+		if ch == nil {
+			t.Fatalf("node for %q not found. Have %s", n, ch.Children())
+		}
+		ch = newCh
+	}
+
+	giNode, ok := ch.Node().(*gitilesNode)
+	if !ok {
+		t.Fatalf("got node type %T, want *gitilesNode", ch.Node())
+	}
+
+	if giNode.clone {
+		t.Errorf("file had clone set.")
+	}
+}
+
+func TestManifestFSTimestamps(t *testing.T) {
+	fix, err := newManifestTestFixture(testManifest)
+	if err != nil {
+		t.Fatal("newTestFixture", err)
+	}
+	defer fix.cleanup()
+
+	var zeroFiles []string
+	if err := filepath.Walk(fix.mntDir, func(n string, fi os.FileInfo, err error) error {
+		if fi != nil && fi.ModTime().UnixNano() == 0 {
+			r, _ := filepath.Rel(fix.mntDir, n)
+			zeroFiles = append(zeroFiles, r)
+		}
+		return nil
+	}); err != nil {
+		t.Fatalf("Walk: %v", err)
+	}
+	if len(zeroFiles) > 0 {
+		sort.Strings(zeroFiles)
+		t.Errorf("found files with zero timestamps: %v", zeroFiles)
+	}
+}
+
+func TestManifestFSBasic(t *testing.T) {
+	fix, err := newManifestTestFixture(testManifest)
+	if err != nil {
+		t.Fatal("newTestFixture", err)
+	}
+	defer fix.cleanup()
+
+	fn := filepath.Join(fix.mntDir, "build", "kati", "AUTHORS")
+	fi, err := os.Lstat(fn)
+	if err != nil {
+		t.Fatalf("Lstat(%s): %v", fn, err)
+	}
+	if fi.Size() != 373 {
+		t.Errorf("got size %d want %d", fi.Size(), 373)
+	}
+
+	contents, err := ioutil.ReadFile(fn)
+	if err != nil {
+		t.Fatalf("ReadFile(%s): %v", fn, err)
+	}
+
+	want := testGitiles["/platform/build/kati/+show/ce34badf691d36e8048b63f89d1a86ee5fa4325c/AUTHORS?format=TEXT"]
+	if string(contents) != want {
+		t.Fatalf("got %q, want %q", contents, want)
+	}
+
+	copyPath := filepath.Join(fix.mntDir, "build", "copydest")
+	if copyFI, err := os.Lstat(copyPath); err != nil {
+		t.Errorf("Lstat(%s): %v", copyPath, err)
+	} else {
+		copyStat := copyFI.Sys().(*syscall.Stat_t)
+		origStat := fi.Sys().(*syscall.Stat_t)
+
+		if !reflect.DeepEqual(copyStat, origStat) {
+			t.Errorf("got stat %v, want %v", copyStat, origStat)
+		}
+	}
+
+	linkPath := filepath.Join(fix.mntDir, "build", "linkdest")
+	if got, err := os.Readlink(linkPath); err != nil {
+		t.Errorf("Readlink(%s): %v", linkPath, err)
+	} else if want := "kati/AUTHORS"; got != want {
+		t.Errorf("Readlink(%s) = %q, want %q", linkPath, got, want)
+	}
+}
+
+func TestManifestFSXMLFile(t *testing.T) {
+	fix, err := newManifestTestFixture(testManifest)
+	if err != nil {
+		t.Fatal("newTestFixture", err)
+	}
+	defer fix.cleanup()
+
+	xmlPath := filepath.Join(fix.mntDir, ".slothfs", "manifest.xml")
+	fuseMF, err := manifest.ParseFile(xmlPath)
+	if err != nil {
+		t.Fatalf("ParseFile(%s): %v", xmlPath, err)
+	}
+
+	if !reflect.DeepEqual(fuseMF, testManifest) {
+		t.Errorf("read back manifest %v, want %v", fuseMF, testManifest)
+	}
+}
+
+type testFixture struct {
+	dir        string
+	mntDir     string
+	server     *fuse.Server
+	cache      *cache.Cache
+	testServer *testServer
+	service    *gitiles.Service
+	root       nodefs.Node
+}
+
+func (f *testFixture) cleanup() {
+	if f.testServer != nil {
+		f.testServer.listener.Close()
+	}
+	if f.server != nil {
+		f.server.Unmount()
+	}
+	os.RemoveAll(f.dir)
+}
+
+func newTestFixture() (*testFixture, error) {
+	d, err := ioutil.TempDir("", "multifstest")
+	if err != nil {
+		return nil, err
+	}
+
+	fixture := &testFixture{dir: d}
+
+	fixture.cache, err = cache.NewCache(filepath.Join(d, "/cache"), cache.Options{})
+	if err != nil {
+		return nil, err
+	}
+
+	fixture.testServer, err = newTestServer()
+	if err != nil {
+		return nil, err
+	}
+
+	fixture.service, err = gitiles.NewService(gitiles.Options{
+		Address: fmt.Sprintf("http://%s", fixture.testServer.addr),
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	return fixture, nil
+}
+
+func (f *testFixture) mount(root nodefs.Node) error {
+	f.mntDir = filepath.Join(f.dir, "mnt")
+	if err := os.Mkdir(f.mntDir, 0755); err != nil {
+		return err
+	}
+
+	fuseOpts := &nodefs.Options{
+		EntryTimeout:    time.Hour,
+		NegativeTimeout: time.Hour,
+		AttrTimeout:     time.Hour,
+	}
+
+	var err error
+	f.server, _, err = nodefs.MountRoot(f.mntDir, root, fuseOpts)
+	if err != nil {
+		return err
+	}
+
+	if fuseDebug {
+		f.server.SetDebug(true)
+	}
+	go f.server.Serve()
+
+	f.root = root
+	return nil
+}
+
+func TestMultiFSBrokenXML(t *testing.T) {
+	fix, err := newTestFixture()
+	if err != nil {
+		t.Fatalf("newTestFixture: %v", err)
+	}
+	defer fix.cleanup()
+
+	brokenXMLFile := filepath.Join(fix.dir, "broken.xml")
+	if err := ioutil.WriteFile(brokenXMLFile, []byte("I'm not XML."), 0644); err != nil {
+		t.Errorf("WriteFile(%s): %v", brokenXMLFile, err)
+	}
+
+	opts := MultiFSOptions{}
+	fs := NewMultiFS(fix.service, fix.cache, opts)
+
+	if err := fix.mount(fs); err != nil {
+		t.Fatalf("mount: %v", err)
+	}
+
+	if err := os.Symlink(brokenXMLFile, filepath.Join(fix.mntDir, "config", "ws")); err == nil {
+		t.Fatalf("want error for broken XML file")
+	}
+}
+
+func TestMultiFSBasic(t *testing.T) {
+	fix, err := newTestFixture()
+	if err != nil {
+		t.Fatalf("newTestFixture: %v", err)
+	}
+	defer fix.cleanup()
+
+	xmlFile := filepath.Join(fix.dir, "manifest.xml")
+	if err := ioutil.WriteFile(xmlFile, []byte(testManifestXML), 0644); err != nil {
+		t.Errorf("WriteFile(%s): %v", xmlFile, err)
+	}
+
+	opts := MultiFSOptions{}
+	fs := NewMultiFS(fix.service, fix.cache, opts)
+
+	if err := fix.mount(fs); err != nil {
+		t.Fatalf("mount: %v", err)
+	}
+
+	wsDir := filepath.Join(fix.mntDir, "ws")
+	if fi, err := os.Lstat(wsDir); err == nil {
+		t.Fatalf("got %v, want non-existent workspace dir", fi)
+	}
+
+	configName := filepath.Join(fix.mntDir, "config", "ws")
+	if err := os.Symlink(xmlFile, configName); err != nil {
+		t.Fatalf("Symlink(%s):  %v", xmlFile, err)
+	}
+
+	if _, err := os.Lstat(wsDir); err != nil {
+		t.Fatalf("Lstat(%s): %v", wsDir, err)
+	}
+
+	if got, err := os.Readlink(configName); err != nil {
+		t.Fatalf("Readlink(%s): %v", configName, err)
+	} else if want := "../ws/.slothfs/manifest.xml"; got != want {
+		t.Errorf("got link %s, want %s", got, want)
+	}
+
+	if _, err := manifest.ParseFile(configName); err != nil {
+		t.Fatalf("ParseFile(%s): %v", configName, err)
+	}
+
+	fn := filepath.Join(wsDir, "build", "kati", "AUTHORS")
+	if fi, err := os.Lstat(fn); err != nil {
+		t.Fatalf("Lstat(%s): %v", fn, err)
+	} else if fi.Size() != 373 {
+		t.Errorf("got %d, want size 373", fi.Size())
+	}
+
+	if err := os.Remove(configName); err != nil {
+		t.Fatalf("Delete(%s): %v", configName, err)
+	}
+
+	if fi, err := os.Lstat(wsDir); err == nil {
+		t.Errorf("Lstat(%s): got %v, want error", wsDir, fi)
+	}
+}
+
+func TestMultiFSManifestDir(t *testing.T) {
+	fix, err := newTestFixture()
+	if err != nil {
+		t.Fatalf("newTestFixture: %v", err)
+	}
+	defer fix.cleanup()
+
+	mfDir := filepath.Join(fix.dir, "manifests")
+	if err := os.MkdirAll(mfDir, 0755); err != nil {
+		t.Fatalf("MkdirAll: %v", err)
+	}
+
+	xmlFile := filepath.Join(mfDir, "ws")
+	if err := ioutil.WriteFile(xmlFile, []byte(testManifestXML), 0644); err != nil {
+		t.Errorf("WriteFile(%s): %v", xmlFile, err)
+	}
+
+	opts := MultiFSOptions{
+		ManifestDir: mfDir,
+	}
+	fs := NewMultiFS(fix.service, fix.cache, opts)
+
+	if err := fix.mount(fs); err != nil {
+		t.Fatalf("mount: %v", err)
+	}
+
+	wsDir := filepath.Join(fix.mntDir, "ws")
+	if _, err := os.Lstat(wsDir); err != nil {
+		t.Fatalf("Lstat(%s): %v", wsDir, err)
+	}
+
+	if err := os.Remove(filepath.Join(fix.mntDir, "config", "ws")); err != nil {
+		t.Fatalf("Remove(config link): %v", err)
+	}
+
+	if fi, err := os.Lstat(filepath.Join(mfDir, "ws")); err == nil {
+		t.Errorf("'ws' still in manifest dir: %v", fi)
+	}
+
+	f, err := ioutil.TempFile("", "")
+	if err != nil {
+		t.Fatalf("TempFile: %v", err)
+	}
+	if err := ioutil.WriteFile(f.Name(), []byte(testManifestXML), 0644); err != nil {
+		t.Errorf("WriteFile(%s): %v", xmlFile, err)
+	}
+
+	configName := filepath.Join(fix.mntDir, "config", "ws2")
+	if err := os.Symlink(f.Name(), configName); err != nil {
+		t.Fatalf("Symlink(%s):  %v", xmlFile, err)
+	}
+
+	// XML file appears again.
+	xmlFile = filepath.Join(mfDir, "ws2")
+	if _, err := os.Stat(xmlFile); err != nil {
+		t.Errorf("Stat(%s): %v", xmlFile, err)
+	}
+}