github.com/mgoltzsche/ctnr@v0.7.1-alpha/pkg/fs/testutils/testutils.go (about) 1 package testutils 2 3 import ( 4 "bytes" 5 "fmt" 6 "os" 7 "path/filepath" 8 "sort" 9 "strings" 10 "testing" 11 "time" 12 13 "github.com/mgoltzsche/ctnr/pkg/fs" 14 "github.com/mgoltzsche/ctnr/pkg/idutils" 15 "github.com/openSUSE/umoci/pkg/fseval" 16 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/require" 18 "github.com/vbatts/go-mtree" 19 ) 20 21 var ( 22 MtreeTestkeywords = []mtree.Keyword{ 23 //"size", 24 "type", 25 "uid", 26 "gid", 27 "mode", 28 "link", 29 "nlink", 30 "xattr", 31 } 32 MtreeTestkeywordsWithTarTime = append(MtreeTestkeywords, "tar_time") 33 ) 34 35 const ExpectedTestfsState = ` 36 # . 37 . size=4096 type=dir mode=0775 tar_time=1519347702.000000000 38 filepathsanitized mode=0640 tar_time=1519347702.000000000 39 # dir1 40 dir1 type=dir mode=0775 nlink=3 tar_time=1519347702.000000000 41 file1 mode=0640 tar_time=1519347702.000000000 42 file2 mode=0640 tar_time=1519347702.000000000 43 44 # dir1/sdir 45 sdir type=dir mode=0775 nlink=3 tar_time=1519347702.000000000 46 nestedsymlink type=link mode=0777 link=nesteddir tar_time=1519347702.000000000 47 48 # dir1/sdir/nesteddir tar_time=1519347702.000000000 49 nesteddir type=dir mode=0750 nlink=2 tar_time=1519347702.000000000 50 .. 51 .. 52 .. 53 # dir2 54 dir2 type=dir mode=0775 nlink=2 tar_time=1519347702.000000000 55 file3 mode=0640 tar_time=1519347702.000000000 56 .. 57 # bin 58 etc type=dir mode=0775 tar_time=1519347702.000000000 59 blockA mode=0640 tar_time=1519347702.000000000 60 chrdevA mode=0640 tar_time=1519347702.000000000 61 fifo mode=0640 tar_time=1519347702.000000000 62 _dirReplacedWithFile mode=0640 tar_time=1519347702.000000000 63 _dirReplacedWithSymlink type=link mode=0777 link=$ROOT/etc/dirB tar_time=1519347702.000000000 64 _linkReplacedWithFile mode=0640 tar_time=1519347702.000000000 65 _symlinkReplacedWithFile mode=0640 tar_time=1519347702.000000000 66 fileA mode=0640 nlink=3 tar_time=1519347702.000000000 67 fileB mode=0640 tar_time=1519347702.000000000 68 link-abs mode=0640 nlink=3 tar_time=1519347702.000000000 69 link-rel mode=0640 nlink=3 tar_time=1519347702.000000000 70 symlink-abs type=link mode=0777 link=$ROOT/etc/dirB tar_time=1519347702.000000000 71 symlink-rel type=link mode=0777 link=../etc/dirA tar_time=1519347702.000000000 72 symlink-sanitized type=link mode=0777 link=.. tar_time=1519347702.000000000 73 # etc/_fileReplacedWithDir 74 _fileReplacedWithDir type=dir mode=0750 tar_time=1519347702.000000000 75 .. 76 # etc/_linkReplacedWithDir 77 _symlinkReplacedWithDir type=dir mode=0750 tar_time=1519347702.000000000 78 .. 79 # etc/dirA 80 dirA type=dir mode=0750 tar_time=1519347702.000000000 81 symlinkResolved1 mode=0640 tar_time=1519347702.000000000 82 # etc/dirA/dirA1 tar_time=1519347702.000000000 83 dirA1 type=dir mode=0750 tar_time=1519347702.000000000 84 symlinkResolved2 mode=0640 tar_time=1519347702.000000000 85 .. 86 .. 87 # etc/dirB 88 dirB type=dir mode=0750 tar_time=1519347702.000000000 89 # etc/dirB/dirB1 90 dirB1 type=dir mode=0750 tar_time=1519347702.000000000 91 .. 92 .. 93 # etc/dirtoberemovedandrewritten 94 dirtoberemovedandrewritten type=dir mode=0750 tar_time=1519347702.000000000 95 .. 96 .. 97 ` 98 99 func WriteTestFileSystem(t *testing.T, testee fs.Writer) (tmpDir string, rootfs string) { 100 // Test basic file, directory, link, fifo, block writes 101 times := fs.FileTimes{} 102 var err error 103 times.Mtime, err = time.Parse(time.RFC3339, "2018-02-23T01:01:42Z") // unix: 1519347702 104 require.NoError(t, err) 105 times.Mtime = time.Unix(times.Mtime.Unix(), 123) 106 times.Atime, err = time.Parse(time.RFC3339, "2018-01-23T01:01:43Z") 107 require.NoError(t, err) 108 dirAttrs := fs.FileAttrs{Mode: os.ModeDir | 0750, UserIds: idutils.UserIds{0, 0}, FileTimes: times} 109 dirAttrsDef := dirAttrs 110 dirAttrsDef.Mode = os.ModeDir | 0775 111 fileAttrsA := fs.NodeAttrs{NodeInfo: fs.NodeInfo{fs.TypeFile, fs.FileAttrs{Mode: 0640, UserIds: idutils.UserIds{0, 0}, Size: 1, FileTimes: times}}} 112 fileAttrsB := fs.NodeAttrs{NodeInfo: fs.NodeInfo{fs.TypeFile, fs.FileAttrs{Mode: 0640, UserIds: idutils.UserIds{0, 0}, Size: 3, FileTimes: times}}} 113 symlinkAttrs := fs.FileAttrs{Mode: os.ModeSymlink, UserIds: idutils.UserIds{0, 0}, FileTimes: times} 114 fifoAttrs := fs.DeviceAttrs{fs.FileAttrs{Mode: os.ModeNamedPipe | 0640, UserIds: idutils.UserIds{0, 0}, FileTimes: times}, 1, 1} 115 chardevAttrs := fs.DeviceAttrs{fs.FileAttrs{Mode: os.ModeCharDevice | 0640, UserIds: idutils.UserIds{0, 0}, FileTimes: times}, 1, 1} 116 blkAttrs := fs.DeviceAttrs{fs.FileAttrs{Mode: os.ModeDevice | 0640, UserIds: idutils.UserIds{0, 0}, FileTimes: times}, 1, 1} 117 fileA := NewSourceMock(fs.TypeFile, fileAttrsA.FileAttrs, "") 118 fileA.NodeAttrs = fileAttrsA 119 fileA.Readable = fs.NewReadableBytes([]byte("a")) 120 fileB := NewSourceMock(fs.TypeFile, fileAttrsB.FileAttrs, "") 121 fileB.NodeAttrs = fileAttrsB 122 fileB.Readable = fs.NewReadableBytes([]byte("bbb")) 123 err = testee.Dir("etc", "", dirAttrsDef) 124 require.NoError(t, err) 125 err = testee.Dir("/", "", dirAttrsDef) 126 require.NoError(t, err) 127 reader, err := testee.File("etc/fileA", fileA) 128 require.NoError(t, err) 129 assert.NotNil(t, reader, "reader returned from File(/etc/fileA)") 130 _, err = testee.File("/etc/fileB", fileB) 131 require.NoError(t, err) 132 err = testee.Dir("etc/dirA", "", dirAttrs) 133 require.NoError(t, err) 134 err = testee.Dir("/etc/dirB", "", dirAttrs) 135 require.NoError(t, err) 136 err = testee.Fifo("/etc/fifo", fifoAttrs) 137 require.NoError(t, err) 138 err = testee.Device("/etc/blockA", blkAttrs) 139 require.NoError(t, err) 140 err = testee.Device("/etc/chrdevA", chardevAttrs) 141 require.NoError(t, err) 142 err = testee.Link("/etc/link-abs", "/etc/fileA") 143 require.NoError(t, err) 144 err = testee.Link("/etc/link-rel", "../etc/fileA") 145 require.NoError(t, err) 146 err = testee.Dir("dir1", "", dirAttrsDef) 147 require.NoError(t, err) 148 _, err = testee.File("/dir1/file1", fileA) 149 require.NoError(t, err) 150 _, err = testee.File("/dir1/file2", fileA) 151 require.NoError(t, err) 152 err = testee.Dir("/dir2", "", dirAttrsDef) 153 require.NoError(t, err) 154 _, err = testee.File("/dir2/file3", fileB) 155 require.NoError(t, err) 156 symlinkAttrs.Symlink = "nesteddir" 157 err = testee.Symlink("/dir1/sdir/nestedsymlink", symlinkAttrs) 158 require.NoError(t, err) 159 err = testee.Dir("/dir1/sdir/nesteddir", "", dirAttrs) 160 require.NoError(t, err) 161 err = testee.Dir("/dir1/sdir", "", dirAttrsDef) 162 require.NoError(t, err) 163 164 // Test symlink resolution 165 symlinkAttrs.Symlink = "../etc/dirA" 166 err = testee.Symlink("etc/symlink-rel", symlinkAttrs) 167 require.NoError(t, err) 168 symlinkAttrs.Symlink = "/etc/dirB" 169 err = testee.Symlink("etc/symlink-abs", symlinkAttrs) 170 require.NoError(t, err) 171 err = testee.Dir("etc/symlink-rel/dirA1", "", dirAttrs) 172 require.NoError(t, err) 173 err = testee.Dir("etc/symlink-abs/dirB1", "", dirAttrs) 174 require.NoError(t, err) 175 _, err = testee.File("etc/symlink-rel/symlinkResolved1", fileA) 176 require.NoError(t, err) 177 _, err = testee.File("/etc/symlink-rel/dirA1/symlinkResolved2", fileA) 178 require.NoError(t, err) 179 180 // Test replacements 181 symlinkAttrs.Symlink = "/etc/dirB" 182 err = testee.Dir("etc/_dirReplacedWithFile", "", dirAttrs) 183 require.NoError(t, err) 184 _, err = testee.File("etc/_dirReplacedWithFile", fileA) 185 require.NoError(t, err) 186 _, err = testee.File("etc/_fileReplacedWithDir", fileA) 187 require.NoError(t, err) 188 err = testee.Dir("etc/_fileReplacedWithDir", "", dirAttrs) 189 require.NoError(t, err) 190 err = testee.Symlink("etc/_dirReplacedWithSymlink", symlinkAttrs) 191 require.NoError(t, err) 192 err = testee.Symlink("etc/_symlinkReplacedWithFile", symlinkAttrs) 193 require.NoError(t, err) 194 _, err = testee.File("etc/_symlinkReplacedWithFile", fileA) 195 require.NoError(t, err) 196 err = testee.Symlink("etc/_symlinkReplacedWithDir", symlinkAttrs) 197 require.NoError(t, err) 198 err = testee.Dir("etc/_symlinkReplacedWithDir", "", dirAttrs) 199 require.NoError(t, err) 200 err = testee.Link("etc/_linkReplacedWithFile", "/etc/symlink-rel/symlinkResolved1") 201 require.NoError(t, err) 202 _, err = testee.File("etc/_linkReplacedWithFile", fileA) 203 require.NoError(t, err) 204 205 // Test remove 206 err = testee.Dir("etc/dirtoberemoved", "", dirAttrs) 207 require.NoError(t, err) 208 err = testee.Remove("etc/dirtoberemoved") 209 require.NoError(t, err) 210 err = testee.Dir("etc/dirA/subdirtoberemoved/nesteddir", "", dirAttrs) 211 require.NoError(t, err) 212 err = testee.Remove("etc/dirA/subdirtoberemoved") 213 require.NoError(t, err) 214 _, err = testee.File("etc/filetoberemoved", fileA) 215 require.NoError(t, err) 216 err = testee.Dir("etc/dirtoberemovedandrewritten", "", dirAttrs) 217 require.NoError(t, err) 218 err = testee.Remove("etc/dirtoberemovedandrewritten") 219 require.NoError(t, err) 220 err = testee.Dir("etc/dirtoberemovedandrewritten", "", dirAttrs) 221 require.NoError(t, err) 222 err = testee.Remove("etc/filetoberemoved") 223 require.NoError(t, err) 224 _, err = testee.File("etc/symlink-rel/dirA1/filetoberemoved", fileA) 225 require.NoError(t, err) 226 err = testee.Remove("etc/symlink-rel/dirA1/filetoberemoved") 227 require.NoError(t, err) 228 err = testee.Symlink("etc/symlink-toberemoved", symlinkAttrs) 229 require.NoError(t, err) 230 err = testee.Remove("etc/symlink-toberemoved") 231 require.NoError(t, err) 232 err = testee.Symlink("etc/link-toberemoved", symlinkAttrs) 233 require.NoError(t, err) 234 err = testee.Remove("etc/link-toberemoved") 235 require.NoError(t, err) 236 237 // Test file system boundaries 238 _, err = testee.File("/../filepathsanitized", fileA) 239 require.NoError(t, err) 240 241 symlinkAttrs.Symlink = filepath.Join("..", "..") 242 err = testee.Symlink("etc/symlink-sanitized", symlinkAttrs) 243 if err != nil { 244 t.Errorf("should not return error when symlinking outside rootfs boundaries (/..): %s", err) 245 t.FailNow() 246 } 247 return 248 } 249 250 func AssertFsState(t *testing.T, rootfs, rootPath string, keywords []mtree.Keyword, expectedDirectoryHierarchy string) { 251 expectedDirectoryHierarchy = strings.Replace(expectedDirectoryHierarchy, "$ROOT", rootPath, -1) 252 expectedDh, err := mtree.ParseSpec(strings.NewReader(expectedDirectoryHierarchy)) 253 require.NoError(t, err) 254 dh, err := mtree.Walk(rootfs, nil, keywords, fseval.DefaultFsEval) 255 require.NoError(t, err) 256 diff, err := mtree.Compare(expectedDh, dh, keywords) 257 require.NoError(t, err) 258 // TODO: do not exclude device files from test but map mocked device files properly back in rootless mode 259 if len(diff) > 0 /* && (diff[0].Path() != "etc/blockA" || diff[0].Type() != mtree.Modified)*/ { 260 var buf bytes.Buffer 261 _, err = dh.WriteTo(&buf) 262 require.NoError(t, err) 263 fmt.Println(string(buf.Bytes())) 264 s := make([]string, len(diff)) 265 for i, c := range diff { 266 s[i] = c.String() 267 } 268 sort.Strings(s) 269 t.Error("Unexpected rootfs differences:\n " + strings.Join(s, "\n ")) 270 t.Fail() 271 } 272 }