1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
|
// Copyright 2013, 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package filetesting
import (
"io/ioutil"
"os"
"path/filepath"
"runtime"
jc "github.com/juju/testing/checkers"
gc "gopkg.in/check.v1"
)
// Entry represents a filesystem entity that can be created; and whose
// correctness can be verified.
type Entry interface {
// GetPath returns the slash-separated relative path that this
// entry represents.
GetPath() string
// Create causes the entry to be created, relative to basePath. It returns
// a copy of the receiver.
Create(c *gc.C, basePath string) Entry
// Check checks that the entry exists, relative to basePath, and matches
// the entry that would be created by Create. It returns a copy of the
// receiver.
Check(c *gc.C, basePath string) Entry
}
var (
_ Entry = Dir{}
_ Entry = File{}
_ Entry = Symlink{}
_ Entry = Removed{}
)
// Entries supplies convenience methods on Entry slices.
type Entries []Entry
// Paths returns the slash-separated path of every entry.
func (e Entries) Paths() []string {
result := make([]string, len(e))
for i, entry := range e {
result[i] = entry.GetPath()
}
return result
}
// Create creates every entry relative to basePath and returns a copy of itself.
func (e Entries) Create(c *gc.C, basePath string) Entries {
result := make([]Entry, len(e))
for i, entry := range e {
result[i] = entry.Create(c, basePath)
}
return result
}
// Check checks every entry relative to basePath and returns a copy of itself.
func (e Entries) Check(c *gc.C, basePath string) Entries {
result := make([]Entry, len(e))
for i, entry := range e {
result[i] = entry.Check(c, basePath)
}
return result
}
// AsRemoveds returns a slice of Removed entries whose paths correspond to
// those in e.
func (e Entries) AsRemoveds() Entries {
result := make([]Entry, len(e))
for i, entry := range e {
result[i] = Removed{entry.GetPath()}
}
return result
}
// join joins a slash-separated path to a filesystem basePath.
func join(basePath, path string) string {
return filepath.Join(basePath, filepath.FromSlash(path))
}
// Dir is an Entry that allows directories to be created and verified. The
// Path field should use "/" as the path separator.
type Dir struct {
Path string
Perm os.FileMode
}
func (d Dir) GetPath() string {
return d.Path
}
func (d Dir) Create(c *gc.C, basePath string) Entry {
path := join(basePath, d.Path)
err := os.MkdirAll(path, d.Perm)
c.Assert(err, gc.IsNil)
err = os.Chmod(path, d.Perm)
c.Assert(err, gc.IsNil)
return d
}
func (d Dir) Check(c *gc.C, basePath string) Entry {
path := join(basePath, d.Path)
fileInfo, err := os.Lstat(path)
comment := gc.Commentf("dir %q", path)
if !c.Check(err, gc.IsNil, comment) {
return d
}
// Skip until we implement proper permissions checking
if runtime.GOOS != "windows" {
c.Check(fileInfo.Mode()&os.ModePerm, gc.Equals, d.Perm, comment)
}
c.Check(fileInfo.Mode()&os.ModeType, gc.Equals, os.ModeDir, comment)
return d
}
// File is an Entry that allows plain files to be created and verified. The
// Path field should use "/" as the path separator.
type File struct {
Path string
Data string
Perm os.FileMode
}
func (f File) GetPath() string {
return f.Path
}
func (f File) Create(c *gc.C, basePath string) Entry {
path := join(basePath, f.Path)
err := ioutil.WriteFile(path, []byte(f.Data), f.Perm)
c.Assert(err, gc.IsNil)
err = os.Chmod(path, f.Perm)
c.Assert(err, gc.IsNil)
return f
}
func (f File) Check(c *gc.C, basePath string) Entry {
path := join(basePath, f.Path)
fileInfo, err := os.Lstat(path)
comment := gc.Commentf("file %q", path)
if !c.Check(err, gc.IsNil, comment) {
return f
}
// Skip until we implement proper permissions checking
if runtime.GOOS != "windows" {
mode := fileInfo.Mode()
c.Check(mode&os.ModeType, gc.Equals, os.FileMode(0), comment)
c.Check(mode&os.ModePerm, gc.Equals, f.Perm, comment)
}
data, err := ioutil.ReadFile(path)
c.Check(err, gc.IsNil, comment)
c.Check(string(data), gc.Equals, f.Data, comment)
return f
}
// Symlink is an Entry that allows symlinks to be created and verified. The
// Path field should use "/" as the path separator.
type Symlink struct {
Path string
Link string
}
func (s Symlink) GetPath() string {
return s.Path
}
func (s Symlink) Create(c *gc.C, basePath string) Entry {
err := os.Symlink(s.Link, join(basePath, s.Path))
c.Assert(err, gc.IsNil)
return s
}
func (s Symlink) Check(c *gc.C, basePath string) Entry {
path := join(basePath, s.Path)
comment := gc.Commentf("symlink %q", path)
link, err := os.Readlink(path)
c.Check(err, gc.IsNil, comment)
c.Check(link, gc.Equals, s.Link, comment)
return s
}
// Removed is an Entry that indicates the absence of any entry. The Path
// field should use "/" as the path separator.
type Removed struct {
Path string
}
func (r Removed) GetPath() string {
return r.Path
}
func (r Removed) Create(c *gc.C, basePath string) Entry {
err := os.RemoveAll(join(basePath, r.Path))
c.Assert(err, gc.IsNil)
return r
}
func (r Removed) Check(c *gc.C, basePath string) Entry {
path := join(basePath, r.Path)
_, err := os.Lstat(path)
// isNotExist allows us to handle the following case:
// File{"foo", ...}.Create(...)
// Removed{"foo/bar"}.Check(...)
// ...where os.IsNotExist would not work.
c.Check(err, jc.Satisfies, isNotExist, gc.Commentf("removed %q", path))
return r
}
|