github.com/Schaudge/grailbase@v0.0.0-20240223061707-44c758a471c0/file/fsnode/fsnodetesting/walk.go (about)

     1  package fsnodetesting
     2  
     3  import (
     4  	"context"
     5  	"io/ioutil"
     6  	"testing"
     7  
     8  	"github.com/Schaudge/grailbase/file/fsnode"
     9  	"github.com/Schaudge/grailbase/ioctx"
    10  	"github.com/grailbio/testutil/assert"
    11  	"github.com/stretchr/testify/require"
    12  )
    13  
    14  // Walker is a collection of settings.
    15  // TODO: Add (Walker).Walk* variant that inspects FileInfo, too, not just content.
    16  type Walker struct {
    17  	IgnoredNames map[string]struct{}
    18  	// Info makes WalkContents return InfoT recursively. See that function.
    19  	Info bool
    20  }
    21  
    22  // T, Parent, and Leaf are aliases to improve readability of fixture definitions.
    23  // InfoT augments T with its FileInfo for tests that want to check mode, size, etc.
    24  type (
    25  	T      = interface{}
    26  	Parent = map[string]T
    27  	Leaf   = []byte
    28  
    29  	InfoT = struct {
    30  		fsnode.FileInfo
    31  		T
    32  	}
    33  )
    34  
    35  // WalkContents traverses all of node and returns map and []byte objects representing
    36  // parents/directories and leaves/files, respectively.
    37  //
    38  // For example, if node is a Parent with children named a and b that are regular files, and an empty
    39  // subdirectory subdir, returns:
    40  //   Parent{
    41  //   	"a":      Leaf("a's content"),
    42  //   	"b":      Leaf("b's content"),
    43  //   	"subdir": Parent{},
    44  //   }
    45  //
    46  // If w.Info, the returned contents will include fsnode.FileInfo, for example:
    47  //   InfoT{
    48  //   	fsnode.NewDirInfo("parent"),
    49  //   	Parent{
    50  //   		"a": InfoT{
    51  //   			fsnode.NewRegInfo("a").WithSize(11),
    52  //   			Leaf("a's content"),
    53  //   		},
    54  //   		"b": InfoT{
    55  //   			fsnode.NewRegInfo("b").WithModePerm(0755),
    56  //   			Leaf("b's content"),
    57  //   		},
    58  //   		"subdir": InfoT{
    59  //   			fsnode.NewDirInfo("subdir")
    60  //   			Parent{},
    61  //   		},
    62  //   	},
    63  //   }
    64  func (w Walker) WalkContents(ctx context.Context, t testing.TB, node fsnode.T) T {
    65  	switch n := node.(type) {
    66  	case fsnode.Parent:
    67  		dir := make(Parent)
    68  		children, err := fsnode.IterateAll(ctx, n.Children())
    69  		require.NoError(t, err)
    70  		for _, child := range children {
    71  			name := child.Info().Name()
    72  			if _, ok := w.IgnoredNames[name]; ok {
    73  				continue
    74  			}
    75  			_, collision := dir[name]
    76  			require.Falsef(t, collision, "name %q is repeated", name)
    77  			dir[name] = w.WalkContents(ctx, t, child)
    78  		}
    79  		if w.Info {
    80  			return InfoT{fsnode.CopyFileInfo(n.Info()), dir}
    81  		}
    82  		return dir
    83  	case fsnode.Leaf:
    84  		leaf := LeafReadAll(ctx, t, n)
    85  		if w.Info {
    86  			return InfoT{fsnode.CopyFileInfo(n.Info()), leaf}
    87  		}
    88  		return leaf
    89  	}
    90  	require.Failf(t, "invalid node type", "node: %T", node)
    91  	panic("unreachable")
    92  }
    93  
    94  func LeafReadAll(ctx context.Context, t testing.TB, n fsnode.Leaf) []byte {
    95  	file, err := fsnode.Open(ctx, n)
    96  	require.NoError(t, err)
    97  	defer func() { assert.NoError(t, file.Close(ctx)) }()
    98  	content, err := ioutil.ReadAll(ioctx.ToStdReader(ctx, file))
    99  	require.NoError(t, err)
   100  	return content
   101  }