...

Source file src/gotest.tools/v3/fs/manifest.go

Documentation: gotest.tools/v3/fs

     1  package fs
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"path/filepath"
     8  
     9  	"gotest.tools/v3/assert"
    10  )
    11  
    12  // Manifest stores the expected structure and properties of files and directories
    13  // in a filesystem.
    14  type Manifest struct {
    15  	root *directory
    16  }
    17  
    18  type resource struct {
    19  	mode os.FileMode
    20  	uid  uint32
    21  	gid  uint32
    22  }
    23  
    24  type file struct {
    25  	resource
    26  	content             io.ReadCloser
    27  	ignoreCariageReturn bool
    28  	compareContentFunc  func(b []byte) CompareResult
    29  }
    30  
    31  func (f *file) Type() string {
    32  	return "file"
    33  }
    34  
    35  type symlink struct {
    36  	resource
    37  	target string
    38  }
    39  
    40  func (f *symlink) Type() string {
    41  	return "symlink"
    42  }
    43  
    44  type directory struct {
    45  	resource
    46  	items         map[string]dirEntry
    47  	filepathGlobs map[string]*filePath
    48  }
    49  
    50  func (f *directory) Type() string {
    51  	return "directory"
    52  }
    53  
    54  type dirEntry interface {
    55  	Type() string
    56  }
    57  
    58  // ManifestFromDir creates a [Manifest] by reading the directory at path. The
    59  // manifest stores the structure and properties of files in the directory.
    60  // ManifestFromDir can be used with [Equal] to compare two directories.
    61  func ManifestFromDir(t assert.TestingT, path string) Manifest {
    62  	if ht, ok := t.(helperT); ok {
    63  		ht.Helper()
    64  	}
    65  
    66  	manifest, err := manifestFromDir(path)
    67  	assert.NilError(t, err)
    68  	return manifest
    69  }
    70  
    71  func manifestFromDir(path string) (Manifest, error) {
    72  	info, err := os.Stat(path)
    73  	switch {
    74  	case err != nil:
    75  		return Manifest{}, err
    76  	case !info.IsDir():
    77  		return Manifest{}, fmt.Errorf("path %s must be a directory", path)
    78  	}
    79  
    80  	directory, err := newDirectory(path, info)
    81  	return Manifest{root: directory}, err
    82  }
    83  
    84  func newDirectory(path string, info os.FileInfo) (*directory, error) {
    85  	items := make(map[string]dirEntry)
    86  	children, err := os.ReadDir(path)
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  	for _, child := range children {
    91  		fullPath := filepath.Join(path, child.Name())
    92  		items[child.Name()], err = getTypedResource(fullPath, child)
    93  		if err != nil {
    94  			return nil, err
    95  		}
    96  	}
    97  
    98  	return &directory{
    99  		resource:      newResourceFromInfo(info),
   100  		items:         items,
   101  		filepathGlobs: make(map[string]*filePath),
   102  	}, nil
   103  }
   104  
   105  func getTypedResource(path string, entry os.DirEntry) (dirEntry, error) {
   106  	info, err := entry.Info()
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  	switch {
   111  	case info.IsDir():
   112  		return newDirectory(path, info)
   113  	case info.Mode()&os.ModeSymlink != 0:
   114  		return newSymlink(path, info)
   115  	// TODO: devices, pipes?
   116  	default:
   117  		return newFile(path, info)
   118  	}
   119  }
   120  
   121  func newSymlink(path string, info os.FileInfo) (*symlink, error) {
   122  	target, err := os.Readlink(path)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  	return &symlink{
   127  		resource: newResourceFromInfo(info),
   128  		target:   target,
   129  	}, err
   130  }
   131  
   132  func newFile(path string, info os.FileInfo) (*file, error) {
   133  	// TODO: defer file opening to reduce number of open FDs?
   134  	readCloser, err := os.Open(path)
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  	return &file{
   139  		resource: newResourceFromInfo(info),
   140  		content:  readCloser,
   141  	}, err
   142  }
   143  

View as plain text