changeset 15:9b4ec6b5c23e

Add tests for multipass files.
author Paul Fisher <paul@pfish.zone>
date Thu, 29 Oct 2015 23:56:53 -0400
parents 4368a377ff64
children bfc035bd5132
files file/file.go file/file_test.go
diffstat 2 files changed, 320 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/file/file.go	Thu Oct 29 21:31:28 2015 -0400
+++ b/file/file.go	Thu Oct 29 23:56:53 2015 -0400
@@ -23,8 +23,13 @@
 )
 
 var (
-	// Raised when
+	// Raised when there's an error in the file format.
 	ErrorBadFile = errors.New("multipass/file: Invalid file format")
+
+	// we spin waiting for the file to become available, doubling our wait time
+	// every time it's unavailable.  If the wait time is longer than this,
+	// give up.  Variable so it can be set in tests.
+	maxDelay = time.Minute
 )
 
 type ShadowFile struct {
@@ -176,7 +181,7 @@
 			if !ok {
 				return nil, err
 			}
-			if errno != syscall.EEXIST || delay > time.Minute {
+			if errno != syscall.EEXIST || delay > maxDelay {
 				return nil, err
 			}
 			time.Sleep(delay)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/file/file_test.go	Thu Oct 29 23:56:53 2015 -0400
@@ -0,0 +1,313 @@
+package file
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path"
+	"testing"
+	"time"
+
+	"pfish.zone/go/multipass/auth"
+)
+
+var tempdir string
+
+const (
+	filemode = 0644
+
+	shadowSpooky   = "123:bogushash:c3Bvb2t5"
+	shadowWhatever = "456:anotherhash:d2hhdGV2ZXI="
+)
+
+var (
+	aSpooky   *auth.Entry
+	aWhatever *auth.Entry
+)
+
+func init() {
+	s, err := auth.EntryFromShadow(shadowSpooky)
+	if err != nil {
+		panic(err.Error())
+	}
+	aSpooky = s
+	w, err := auth.EntryFromShadow(shadowWhatever)
+	if err != nil {
+		panic(err.Error())
+	}
+	aWhatever = w
+}
+
+func TestMain(m *testing.M) {
+	dir, err := ioutil.TempDir(os.TempDir(), "multipass-")
+	maxDelay = 2 * time.Second
+	tempdir = dir
+	if err != nil {
+		panic("couldn't create temp dir")
+	}
+	m.Run()
+	os.RemoveAll(tempdir)
+}
+
+type testfile struct {
+	t   *testing.T
+	dir string
+}
+
+func mktest(t *testing.T, dir string) *testfile {
+	f := &testfile{t, dir}
+	os.Mkdir(f.dirpath(), 0700)
+	return f
+}
+
+func (f *testfile) dirpath() string {
+	return path.Join(tempdir, f.dir)
+}
+
+func (f *testfile) filepath() string {
+	return path.Join(f.dirpath(), "file")
+}
+
+func (f *testfile) read() string {
+	contents, err := ioutil.ReadFile(f.filepath())
+	if err != nil {
+		f.t.Fatalf("couldn't read output file: %q", err.Error())
+	}
+	return string(contents)
+}
+
+func (f *testfile) write(data string) {
+	err := ioutil.WriteFile(f.filepath(), []byte(data), filemode)
+	if err != nil {
+		f.t.Fatalf("couldn't write output file: %q", err)
+	}
+}
+
+func (f *testfile) perms() os.FileMode {
+	stat, err := os.Stat(f.filepath())
+	if err != nil {
+		f.t.Fatalf("couldn't stat output file: %q", err)
+	}
+	return stat.Mode().Perm()
+}
+
+func (f *testfile) cleanup() {
+	os.RemoveAll(f.dirpath())
+}
+
+func TestCreate(t *testing.T) {
+	f := mktest(t, "create")
+	defer f.cleanup()
+	s := New(f.filepath())
+	err := s.Add(aSpooky)
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+	if f.perms() != filemode {
+		t.Errorf("want perms %q; got %q", filemode, f.perms())
+	}
+	want := fmt.Sprintf("%s\n%s\n", Banner, shadowSpooky)
+	got := f.read()
+	if want != got {
+		t.Errorf("bad file contents: want %q; got %q", want, got)
+	}
+}
+
+func TestAppend(t *testing.T) {
+	f := mktest(t, "append")
+	defer f.cleanup()
+	f.write(fmt.Sprintf("%s\n%s\n", Banner, shadowSpooky))
+	s := New(f.filepath())
+	err := s.Add(aWhatever)
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+	want := fmt.Sprintf("%s\n%s\n%s\n", Banner, shadowWhatever, shadowSpooky)
+	got := f.read()
+	if want != got {
+		t.Errorf("bad file contents: want %q; got %q", want, got)
+	}
+}
+
+func TestRemove(t *testing.T) {
+	f := mktest(t, "remove")
+	defer f.cleanup()
+	f.write(fmt.Sprintf("%s\n%s\n%s\n", Banner, shadowWhatever, shadowSpooky))
+	s := New(f.filepath())
+	err := s.Remove(456)
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+	want := fmt.Sprintf("%s\n%s\n", Banner, shadowSpooky)
+	got := f.read()
+	if want != got {
+		t.Errorf("bad file contents: want %q; got %q", want, got)
+	}
+
+	// Removing a nonexistent entry is idempotent.
+	err = s.Remove(456)
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+	got = f.read()
+	if want != got {
+		t.Errorf("bad file contents: want %q; got %q", want, got)
+	}
+
+	// Removing the remaining entry leaves an empty file.
+	err = s.Remove(123)
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+	want = fmt.Sprintf("%s\n", Banner)
+	got = f.read()
+	if want != got {
+		t.Errorf("bad file contents: want %q; got %q", want, got)
+	}
+}
+
+func TestList(t *testing.T) {
+	f := mktest(t, "list")
+	defer f.cleanup()
+	s := New(f.filepath())
+	entries, err := s.AllEntries()
+	if len(entries) != 0 {
+		t.Errorf("want 0 entries; got %q", entries)
+	}
+
+	f.write(fmt.Sprintf("%s\n%s\n%s\n", Banner, shadowWhatever, shadowSpooky))
+	entries, err = s.AllEntries()
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+	if len(entries) != 2 {
+		t.Fatalf("want len(entries) == 2; got entries = %q", entries)
+	}
+	if *entries[0] != *aWhatever || *entries[1] != *aSpooky {
+		t.Fatalf("want {aWhatever, aSpooky}; got %q", entries)
+	}
+}
+
+func TestAuthenticate(t *testing.T) {
+	f := mktest(t, "auth")
+	defer f.cleanup()
+	s := New(f.filepath())
+	eA, passA, err := auth.NewEntry("a")
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+	eB, passB, err := auth.NewEntry("b")
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+	err = s.Add(eA)
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+	err = s.Add(eB)
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+	good, err := s.Authenticate(passA)
+	if !good {
+		t.Errorf("couldn't authenticate password A")
+	}
+	good, err = s.Authenticate(passB)
+	if !good {
+		t.Errorf("couldn't authenticate password B")
+	}
+	bad, err := s.Authenticate("boo")
+	if bad {
+		t.Errorf("authenticated bad password")
+	}
+}
+
+type clobberEvent int
+
+const (
+	unlocked clobberEvent = iota
+	written
+	failed
+)
+
+func TestNoClobber(t *testing.T) {
+	f := mktest(t, "clobber")
+	defer f.cleanup()
+	f.write(fmt.Sprintf("%s\n", Banner))
+	s := New(f.filepath())
+	// put a file in the system's new-file so it won't overwrite it.
+	err := ioutil.WriteFile(s.newFilename(), nil, filemode)
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+	events := make(chan clobberEvent, 10)
+	go func() {
+		err := s.Add(aWhatever)
+		if err != nil {
+			events <- failed
+			return
+		}
+		events <- written
+	}()
+	time.Sleep(time.Second)
+	events <- unlocked
+	err = os.Remove(s.newFilename())
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+	ev := <-events
+	if unlocked != ev {
+		t.Errorf("event 0: %q; got %q", unlocked, ev)
+	}
+	ev = <-events
+	if written != ev {
+		t.Errorf("event 1: %q; got %q", written, ev)
+	}
+
+	want := fmt.Sprintf("%s\n%s\n", Banner, shadowWhatever)
+	got := f.read()
+	if want != got {
+		t.Errorf("bad contents: want %q; got %q", want, got)
+	}
+}
+
+func TestGiveUp(t *testing.T) {
+	f := mktest(t, "giveup")
+	defer f.cleanup()
+	f.write(fmt.Sprintf("%s\n", Banner))
+	s := New(f.filepath())
+	// put a file in the system's new-file so it won't overwrite it.
+	err := ioutil.WriteFile(s.newFilename(), nil, filemode)
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+	events := make(chan clobberEvent, 10)
+	go func() {
+		err := s.Add(aWhatever)
+		if err != nil {
+			events <- failed
+			return
+		}
+		events <- written
+	}()
+	time.Sleep(5 * time.Second)
+	events <- unlocked
+	err = os.Remove(s.newFilename())
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+	ev := <-events
+	if failed != ev {
+		t.Errorf("event 0: want %q; got %q", failed, ev)
+	}
+	ev = <-events
+	if unlocked != ev {
+		t.Errorf("event 1: want %q; got %q", unlocked, ev)
+	}
+
+	want := fmt.Sprintf("%s\n", Banner)
+	got := f.read()
+	if want != got {
+		t.Errorf("bad contents: want %q; got %q", want, got)
+	}
+}