blob: b17ddf9390883260803c68f7a0ed8399615d77ca [file] [log] [blame]
// 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"
"os/exec"
"path/filepath"
"reflect"
"sort"
"strings"
"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 TestManifestFSGitRepoSeedsTreeCache(t *testing.T) {
fix, err := newTestFixture()
if err != nil {
t.Fatal("newTestFixture", err)
}
defer fix.cleanup()
// Add a git repo.
cmd := exec.Command("/bin/sh", "-c",
strings.Join([]string{
"mkdir -p localhost/platform/build/kati.git",
"cd localhost/platform/build/kati.git",
"git init",
"touch file",
"git add file",
"git commit -m msg -a",
"git show --no-patch --pretty=format:'%H' HEAD | head",
}, " && "))
cmd.Dir = filepath.Join(fix.dir, "cache", "git")
headSHA1 := ""
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("create repo: %v, out: %s", err, string(out))
} else {
lines := strings.Split(string(out), "\n")
headSHA1 = lines[len(lines)-1]
}
p := testManifest.Project[0]
p.CloneURL = "file:///localhost/platform/build/kati"
p.Revision = headSHA1
mf := *testManifest
mf.Project = nil
mf.Project = append(mf.Project, p)
opts := ManifestOptions{
Manifest: &mf,
}
fs, err := NewManifestFS(fix.service, fix.cache, opts)
if err != nil {
t.Fatal("NewManifestFS", err)
}
if err := fix.mount(fs); err != nil {
t.Fatal("mount", err)
}
headID, err := parseID(headSHA1)
if err != nil {
t.Fatalf("parseID(%s): %v", headSHA1, err)
}
tree, err := fix.cache.Tree.Get(headID)
if err != nil {
t.Fatalf("treeCache.Get(%s): %v", headSHA1, err)
}
newInt := func(n int) *int { return &n }
tree.ID = ""
want := &gitiles.Tree{
Entries: []gitiles.TreeEntry{
{
Name: "file",
Mode: 0100644,
Type: "blob",
ID: "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391",
Size: newInt(0),
},
},
}
if !reflect.DeepEqual(tree, want) {
t.Errorf("got cached tree %v, want %v", tree, want)
}
}
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("", "slothfs")
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 TestMultiManifestFSBrokenXML(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 := MultiManifestFSOptions{}
fs := NewMultiManifestFS(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 TestMultiManifestFSBasic(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 := MultiManifestFSOptions{}
fs := NewMultiManifestFS(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 TestMultiManifestFSManifestDir(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 := MultiManifestFSOptions{
ManifestDir: mfDir,
}
fs := NewMultiManifestFS(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)
}
}