github.com/wtsi-ssg/wrstat/v4@v4.5.1/neaten/neaten_test.go (about) 1 /******************************************************************************* 2 * Copyright (c) 2022 Genome Research Ltd. 3 * 4 * Author: Sendu Bala <sb10@sanger.ac.uk> 5 * Author: Kyle Mace <km34@sanger.ac.uk> 6 * 7 * Permission is hereby granted, free of charge, to any person obtaining 8 * a copy of this software and associated documentation files (the 9 * "Software"), to deal in the Software without restriction, including 10 * without limitation the rights to use, copy, modify, merge, publish, 11 * distribute, sublicense, and/or sell copies of the Software, and to 12 * permit persons to whom the Software is furnished to do so, subject to 13 * the following conditions: 14 * 15 * The above copyright notice and this permission notice shall be included 16 * in all copies or substantial portions of the Software. 17 * 18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 ******************************************************************************/ 26 27 package neaten 28 29 import ( 30 "io/fs" 31 "os" 32 "path/filepath" 33 "syscall" 34 "testing" 35 "time" 36 37 . "github.com/smartystreets/goconvey/convey" 38 ) 39 40 const modePermUser = 0770 41 42 func TestTidy(t *testing.T) { //nolint:gocognit 43 date := "20220829" 44 srcUniversal := "cci4fafnu1ia052l75sg" 45 srcUniqueGo := "cci4fafnu1ia052l75t0" 46 srcUniquePerl := "cci4fafnu1ia052l75tg" 47 48 Convey("Given existing source and dest dirs you can tidy the source", t, func() { 49 tmpDir := t.TempDir() 50 srcDir := filepath.Join(tmpDir, "src", srcUniversal) 51 destDir := filepath.Join(tmpDir, "dest", "final") 52 interestUniqueDir1 := createTestPath([]string{srcDir, "go", srcUniqueGo}) 53 interestUniqueDir2 := createTestPath([]string{srcDir, "perl", srcUniquePerl}) 54 55 combineSuffixes, dbSuffixes, baseSuffixes := buildSrcDir(srcDir, srcUniqueGo, srcUniquePerl, 56 interestUniqueDir1, interestUniqueDir2) 57 58 createTestDirWithDifferentPerms(destDir) 59 60 test := Tidy{ 61 SrcDir: srcDir, 62 DestDir: destDir, 63 Date: date, 64 65 CombineFileSuffixes: combineSuffixes, 66 DBFileSuffixes: dbSuffixes, 67 BaseFileSuffixes: baseSuffixes, 68 69 CombineFileGlobPattern: "%s/*/*/%s", 70 DBFileGlobPattern: "%s/*/*/%s", 71 WalkFilePathGlobPattern: "%s/*/*/*%s", 72 73 DestDirPerms: modePermUser, 74 } 75 76 disableDeletion := false 77 78 err := test.Up(disableDeletion) 79 80 Convey("And the combine files are moved from the source dir to the dest dir", func() { 81 combineFileSuffixes := [4]string{".logs.gz", ".byusergroup.gz", ".bygroup", ".stats.gz"} 82 83 for _, suffix := range combineFileSuffixes { 84 final1 := filepath.Join(destDir, date+"_go."+srcUniqueGo+"."+srcUniversal+suffix) 85 _, err = os.Stat(final1) 86 So(err, ShouldBeNil) 87 88 final2 := filepath.Join(destDir, date+"_perl."+srcUniquePerl+"."+srcUniversal+suffix) 89 _, err = os.Stat(final2) 90 So(err, ShouldBeNil) 91 } 92 }) 93 94 Convey("And the contents of the .basedirs and .dgut.dbs dir exist", func() { 95 dbsPath := filepath.Join(destDir, date+"_"+srcUniversal) 96 dbsSuffixes := [5]string{ 97 ".basedirs", 98 ".dgut.dbs/0/dgut.db", 99 ".dgut.dbs/0/dgut.db.children", 100 ".dgut.dbs/1/dgut.db", 101 ".dgut.dbs/1/dgut.db.children"} 102 103 for _, suffix := range dbsSuffixes { 104 _, err = os.Stat(dbsPath + suffix) 105 So(err, ShouldBeNil) 106 } 107 }) 108 109 Convey("And the .dgut.dbs.updated file exists in the dest dir", func() { 110 expectedFileName := filepath.Join(destDir, ".dgut.dbs.updated") 111 112 _, err = os.Stat(expectedFileName) 113 So(err, ShouldBeNil) 114 }) 115 116 Convey("And the mtime of the .dgut.dbs file matches the oldest mtime of the walk log files", func() { 117 err = os.RemoveAll(tmpDir) 118 So(err, ShouldBeNil) 119 120 buildSrcDir(srcDir, srcUniqueGo, srcUniquePerl, interestUniqueDir1, interestUniqueDir2) 121 122 createTestDirWithDifferentPerms(destDir) 123 124 newMtimeFile := filepath.Join(interestUniqueDir1, "walk.1.log") 125 expectedMTime := time.Date(2006, time.April, 1, 3, 4, 5, 0, time.UTC) 126 expectedATime := time.Date(2007, time.March, 2, 4, 5, 6, 0, time.UTC) 127 128 err = os.Chtimes(newMtimeFile, expectedATime, expectedMTime) 129 So(err, ShouldBeNil) 130 131 err = test.Up(disableDeletion) 132 So(err, ShouldBeNil) 133 134 dbsFileMTime := getMTime(filepath.Join(destDir, ".dgut.dbs.updated")) 135 136 So(dbsFileMTime, ShouldEqual, expectedMTime) 137 }) 138 139 Convey("And the moved file permissions match those of the dest dir", func() { 140 destDirPerm, errs := os.Stat(destDir) 141 So(errs, ShouldBeNil) 142 143 err = filepath.WalkDir(destDir, func(path string, d fs.DirEntry, err error) error { 144 if err != nil { 145 return err 146 } 147 148 pathPerm, err := os.Stat(path) 149 if err != nil { 150 return err 151 } 152 153 So(permissionsAndOwnershipSame(destDirPerm, pathPerm), ShouldBeTrue) 154 155 return nil 156 }) 157 So(err, ShouldBeNil) 158 }) 159 160 Convey("And up deletes the source directory after the files have been moved", func() { 161 err = os.RemoveAll(tmpDir) 162 So(err, ShouldBeNil) 163 164 buildSrcDir(srcDir, srcUniqueGo, srcUniquePerl, interestUniqueDir1, interestUniqueDir2) 165 166 createTestDirWithDifferentPerms(destDir) 167 168 err = test.Up(disableDeletion) 169 So(err, ShouldBeNil) 170 171 _, err = os.Stat(srcDir) 172 So(err, ShouldNotBeNil) 173 }) 174 175 Convey("And up does not delete the source directory after the files have been moved if the arg is false", func() { 176 err = os.RemoveAll(tmpDir) 177 So(err, ShouldBeNil) 178 179 buildSrcDir(srcDir, srcUniqueGo, srcUniquePerl, interestUniqueDir1, interestUniqueDir2) 180 181 createTestDirWithDifferentPerms(destDir) 182 183 err = test.Up(true) 184 So(err, ShouldBeNil) 185 186 _, err = os.Stat(srcDir) 187 So(err, ShouldBeNil) 188 }) 189 190 Convey("And it also works if the dest dir doesn't exist", func() { 191 err := os.RemoveAll(destDir) 192 So(err, ShouldBeNil) 193 194 err = os.RemoveAll(srcDir) 195 So(err, ShouldBeNil) 196 197 buildSrcDir(srcDir, srcUniqueGo, srcUniquePerl, interestUniqueDir1, interestUniqueDir2) 198 199 err = test.Up(disableDeletion) 200 So(err, ShouldBeNil) 201 202 _, err = os.Stat(destDir) 203 So(err, ShouldBeNil) 204 }) 205 206 Convey("And it doesn't work if source dir doesn't exist", func() { 207 err := os.RemoveAll(srcDir) 208 So(err, ShouldBeNil) 209 210 err = test.Up(disableDeletion) 211 So(err, ShouldNotBeNil) 212 213 _, err = os.Stat(srcDir) 214 So(err, ShouldNotBeNil) 215 }) 216 217 Convey("And it doesn't work if source or dest is an incorrect relative path", func() { 218 err := os.RemoveAll(destDir) 219 So(err, ShouldBeNil) 220 221 err = os.RemoveAll(srcDir) 222 So(err, ShouldBeNil) 223 224 buildSrcDir(srcDir, srcUniqueGo, srcUniquePerl, interestUniqueDir1, interestUniqueDir2) 225 226 relDir := filepath.Join(tmpDir, "rel") 227 err = os.MkdirAll(relDir, modePermUser) 228 So(err, ShouldBeNil) 229 230 err = os.Chdir(relDir) 231 So(err, ShouldBeNil) 232 233 err = os.RemoveAll(relDir) 234 So(err, ShouldBeNil) 235 236 test.SrcDir = "../src/" + srcUniversal 237 test.DestDir = "../dest/final" 238 239 err = test.Up(disableDeletion) 240 So(err, ShouldNotBeNil) 241 }) 242 }) 243 } 244 245 func buildSrcDir(srcDir, srcUniqueGo, srcUniquePerl, interestUniqueDir1, interestUniqueDir2 string) (map[string]string, 246 map[string]string, map[string]string) { 247 walkFileSuffixes := [5]string{"log", "byusergroup", "bygroup", "stats", "dgut"} 248 combineFileSuffixes := [4]string{"combine.log.gz", "combine.byusergroup.gz", "combine.bygroup", "combine.stats.gz"} 249 250 for i := range walkFileSuffixes { 251 createTestPath([]string{interestUniqueDir1}, "walk.1."+walkFileSuffixes[i]) 252 createTestPath([]string{interestUniqueDir2}, "walk.1."+walkFileSuffixes[i]) 253 } 254 255 for i := range combineFileSuffixes { 256 createTestPath([]string{interestUniqueDir1}, combineFileSuffixes[i]) 257 createTestPath([]string{interestUniqueDir2}, combineFileSuffixes[i]) 258 } 259 260 goDBDir := []string{srcDir, "go", srcUniqueGo, "combine.dgut.db"} 261 perlDBDir := []string{srcDir, "perl", srcUniquePerl, "combine.dgut.db"} 262 combineDirSuffixes := [2]string{"dgut.db", "dgut.db.children"} 263 264 for i := range combineDirSuffixes { 265 createTestPath(goDBDir, combineDirSuffixes[i]) 266 createTestPath(perlDBDir, combineDirSuffixes[i]) 267 } 268 269 createTestPath([]string{srcDir}, "base.dirs") 270 271 inputOutputCombineSuffixes := map[string]string{ 272 combineFileSuffixes[0]: "logs.gz", 273 combineFileSuffixes[1]: "byusergroup.gz", 274 combineFileSuffixes[2]: "bygroup", 275 combineFileSuffixes[3]: "stats.gz", 276 } 277 278 inputOutputDBSuffixes := map[string]string{ 279 "combine.dgut.db": "dgut.dbs"} 280 281 inputOutputBaseSuffixes := map[string]string{ 282 "base.dirs": "basedirs"} 283 284 return inputOutputCombineSuffixes, inputOutputDBSuffixes, inputOutputBaseSuffixes 285 } 286 287 // createTestPath takes a set of subdirectory names and an optional file 288 // basename and creates a directory and empty file out of them. Returns the 289 // directory. 290 func createTestPath(dirs []string, basename ...string) string { 291 wholeDir := filepath.Join(dirs...) 292 293 err := os.MkdirAll(wholeDir, modePermUser) 294 So(err, ShouldBeNil) 295 296 if len(basename) == 1 { 297 err = createFile(filepath.Join(wholeDir, basename[0])) 298 So(err, ShouldBeNil) 299 } 300 301 return wholeDir 302 } 303 304 // createTestDirWithDifferentPerms creates the given directory with different 305 // group ownership and rw permissions than normal. 306 func createTestDirWithDifferentPerms(dir string) { 307 err := os.MkdirAll(dir, 0777) 308 So(err, ShouldBeNil) 309 310 destUID := os.Getuid() 311 destGroups, err := os.Getgroups() 312 So(err, ShouldBeNil) 313 314 err = os.Lchown(dir, destUID, destGroups[1]) 315 So(err, ShouldBeNil) 316 } 317 318 // getMTime takes a filePath and returns its Mtime. 319 func getMTime(filePath string) time.Time { 320 FileInfo, err := os.Stat(filePath) 321 So(err, ShouldBeNil) 322 323 fileMTime := FileInfo.ModTime() 324 325 return fileMTime 326 } 327 328 // permissionsAndOwnershipSame takes two fileinfos and returns whether their 329 // permissions and ownerships are the same. 330 func permissionsAndOwnershipSame(a, b fs.FileInfo) bool { 331 return readWritePermissionsSame(a, b) && userAndGroupOwnershipSame(a, b) 332 } 333 334 // userAndGroupOwnershipSame tests if the given fileinfos have the same UID and 335 // GID. 336 func userAndGroupOwnershipSame(a, b fs.FileInfo) bool { 337 aUID, aGID := getUIDAndGID(a) 338 bUID, bGID := getUIDAndGID(b) 339 340 return aUID == bUID && aGID == bGID 341 } 342 343 // matchReadWrite ensures that the given file with the current fileinfo has the 344 // same user,group,other read&write permissions as the desired fileinfo. 345 func readWritePermissionsSame(a, b fs.FileInfo) bool { 346 aRW := a.Mode() & modeRW 347 bRW := b.Mode() & modeRW 348 349 return aRW == bRW 350 } 351 352 func TestTouch(t *testing.T) { 353 Convey("Touch updates a file's a and mtime to now, in local time", t, func() { 354 tdir := t.TempDir() 355 path := filepath.Join(tdir, "file") 356 357 err := createFile(path) 358 So(err, ShouldBeNil) 359 360 before := time.Now().Add(-10 * time.Second) 361 err = os.Chtimes(path, before, before) 362 So(err, ShouldBeNil) 363 364 recent := time.Now() 365 err = Touch(path) 366 So(err, ShouldBeNil) 367 368 info, err := os.Stat(path) 369 So(err, ShouldBeNil) 370 371 a := info.Sys().(*syscall.Stat_t).Atim //nolint:forcetypeassert 372 atime := time.Unix(a.Sec, a.Nsec) 373 374 So(atime, ShouldHappenAfter, recent) 375 So(info.ModTime(), ShouldHappenAfter, recent) 376 So(atime, ShouldEqual, info.ModTime()) 377 }) 378 } 379 func TestDeleteAllPrefixedFiles(t *testing.T) { 380 Convey("Only files with a matching prefix should be deleted", t, func() { 381 tdir := t.TempDir() 382 for _, name := range []string{"aaa", "aab", "baa"} { 383 path := filepath.Join(tdir, name) 384 err := createFile(path) 385 So(err, ShouldBeNil) 386 } 387 388 err := DeleteAllPrefixedDirEntries(tdir, "a") 389 So(err, ShouldBeNil) 390 391 entries, err := os.ReadDir(tdir) 392 So(err, ShouldBeNil) 393 So(len(entries), ShouldEqual, 1) 394 So(entries[0].Name(), ShouldEqual, "baa") 395 }) 396 }