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  }