github.com/quay/claircore@v1.5.28/test/generate.go (about) 1 package test 2 3 import ( 4 "errors" 5 "io/fs" 6 "os" 7 "path/filepath" 8 "strings" 9 "testing" 10 "time" 11 12 "github.com/quay/claircore/test/integration" 13 ) 14 15 // GenerateFixture is a helper for generating a test fixture. A path that can be 16 // used to open the file is returned. 17 // 18 // If the test fails, the cached file is removed. 19 // It is the caller's responsibility to ensure that "name" is unique per-package. 20 func GenerateFixture(t testing.TB, name string, stamp time.Time, gen func(testing.TB, *os.File)) string { 21 t.Helper() 22 if !fs.ValidPath(name) || strings.Contains(name, "/") { 23 t.Fatalf(`can't use "name" as a filename: %q`, name) 24 } 25 root := integration.CacheDir(t) 26 // Generated fixtures are stored in the per-package cache. 27 p := filepath.Join(integration.PackageCacheDir(t), name) 28 // Nice name 29 n, err := filepath.Rel(root, p) 30 if err != nil { 31 t.Fatal(err) 32 } 33 t.Cleanup(func() { 34 if t.Failed() { 35 t.Logf("generated file %q: removing due to failed test", n) 36 if err := os.Remove(p); err != nil { 37 t.Errorf("generated file %q: unexpected remove error: %v", n, err) 38 } 39 } 40 }) 41 fi, err := os.Stat(p) 42 switch { 43 case err == nil && !fi.ModTime().Before(stamp): // not before to get ">=" 44 t.Logf("generated file %q: up to date", n) 45 return p 46 case err == nil && fi.ModTime().Before(stamp): 47 case errors.Is(err, os.ErrNotExist): 48 default: 49 t.Fatalf("generated file %q: unexpected stat error: %v", n, err) 50 } 51 52 f, err := os.Create(p) 53 if err != nil { 54 t.Fatalf("generated file %q: unexpected create error: %v", n, err) 55 } 56 defer f.Close() 57 58 gen(t, f) 59 return p 60 } 61 62 // Modtime is a helper for picking a timestamp to use with [GenerateFixture] and 63 // [GenerateLayer]. 64 // 65 // It reports the modtime of the passed path. If the file does not exist, the 66 // start of the UNIX epoch is returned. If the file is not regular or a 67 // directory, the test is failed. If the file is a directory, the newest time of 68 // all the entries is reported. 69 func Modtime(t testing.TB, path string) time.Time { 70 t.Helper() 71 fi, err := os.Stat(path) 72 switch { 73 case errors.Is(err, nil): 74 case errors.Is(err, os.ErrNotExist): 75 return time.UnixMilli(0) 76 default: 77 t.Fatalf("modtime: unexpected stat error: %v", err) 78 } 79 switch m := fi.Mode(); { 80 case m.IsRegular(): 81 return fi.ModTime() 82 case m.IsDir(): // Fall out of switch 83 default: 84 t.Fatalf("modtime: unexpected file mode: %v", m) 85 } 86 87 // Called on dir, pick the latest time of all the children. 88 // Do this the verbose way to avoid the sort incurred by [os.ReadDir]. 89 d, err := os.Open(path) 90 if err != nil { 91 t.Fatalf("modtime: unexpected open error: %v", err) 92 } 93 defer d.Close() 94 ents, err := d.ReadDir(0) 95 if err != nil { 96 t.Fatalf("modtime: unexpected readdir error: %v", err) 97 } 98 stamp := time.UnixMilli(0) 99 for _, e := range ents { 100 fi, err := e.Info() 101 if err != nil { 102 t.Fatalf("modtime: unexpected dirent stat error: %v", err) 103 } 104 if mt := fi.ModTime(); mt.After(stamp) { 105 stamp = mt 106 } 107 } 108 return stamp 109 }