github.com/mgoltzsche/ctnr@v0.7.1-alpha/pkg/fs/testutils/testutils.go (about)

     1  package testutils
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"sort"
     9  	"strings"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/mgoltzsche/ctnr/pkg/fs"
    14  	"github.com/mgoltzsche/ctnr/pkg/idutils"
    15  	"github.com/openSUSE/umoci/pkg/fseval"
    16  	"github.com/stretchr/testify/assert"
    17  	"github.com/stretchr/testify/require"
    18  	"github.com/vbatts/go-mtree"
    19  )
    20  
    21  var (
    22  	MtreeTestkeywords = []mtree.Keyword{
    23  		//"size",
    24  		"type",
    25  		"uid",
    26  		"gid",
    27  		"mode",
    28  		"link",
    29  		"nlink",
    30  		"xattr",
    31  	}
    32  	MtreeTestkeywordsWithTarTime = append(MtreeTestkeywords, "tar_time")
    33  )
    34  
    35  const ExpectedTestfsState = `
    36  	# .
    37  	. size=4096 type=dir mode=0775 tar_time=1519347702.000000000
    38  		filepathsanitized mode=0640 tar_time=1519347702.000000000
    39  	# dir1
    40  	dir1 type=dir mode=0775 nlink=3 tar_time=1519347702.000000000
    41  	    file1 mode=0640 tar_time=1519347702.000000000
    42  	    file2 mode=0640 tar_time=1519347702.000000000
    43  
    44  	# dir1/sdir
    45  	sdir type=dir mode=0775 nlink=3 tar_time=1519347702.000000000
    46  		nestedsymlink type=link mode=0777 link=nesteddir tar_time=1519347702.000000000
    47  
    48  	# dir1/sdir/nesteddir tar_time=1519347702.000000000
    49  	nesteddir type=dir mode=0750 nlink=2 tar_time=1519347702.000000000
    50  	..
    51  	..
    52  	..
    53  	# dir2
    54  	dir2 type=dir mode=0775 nlink=2 tar_time=1519347702.000000000
    55  	    file3 mode=0640 tar_time=1519347702.000000000
    56  	..
    57  	# bin
    58  	etc type=dir mode=0775 tar_time=1519347702.000000000
    59  		blockA mode=0640 tar_time=1519347702.000000000
    60  		chrdevA mode=0640 tar_time=1519347702.000000000
    61  		fifo mode=0640 tar_time=1519347702.000000000
    62  		_dirReplacedWithFile mode=0640 tar_time=1519347702.000000000
    63  		_dirReplacedWithSymlink type=link mode=0777 link=$ROOT/etc/dirB tar_time=1519347702.000000000
    64  		_linkReplacedWithFile mode=0640 tar_time=1519347702.000000000
    65  		_symlinkReplacedWithFile mode=0640 tar_time=1519347702.000000000
    66  		fileA mode=0640 nlink=3 tar_time=1519347702.000000000
    67  		fileB mode=0640 tar_time=1519347702.000000000
    68  		link-abs mode=0640 nlink=3 tar_time=1519347702.000000000
    69  		link-rel mode=0640 nlink=3 tar_time=1519347702.000000000
    70  		symlink-abs type=link mode=0777 link=$ROOT/etc/dirB tar_time=1519347702.000000000
    71  		symlink-rel type=link mode=0777 link=../etc/dirA tar_time=1519347702.000000000
    72  		symlink-sanitized type=link mode=0777 link=.. tar_time=1519347702.000000000
    73  	# etc/_fileReplacedWithDir
    74  	_fileReplacedWithDir type=dir mode=0750 tar_time=1519347702.000000000
    75  	..
    76  	# etc/_linkReplacedWithDir
    77  	_symlinkReplacedWithDir type=dir mode=0750 tar_time=1519347702.000000000
    78  	..
    79  	# etc/dirA
    80  	dirA type=dir mode=0750 tar_time=1519347702.000000000
    81  		symlinkResolved1 mode=0640 tar_time=1519347702.000000000
    82  	# etc/dirA/dirA1 tar_time=1519347702.000000000
    83  	dirA1 type=dir mode=0750 tar_time=1519347702.000000000
    84  		symlinkResolved2 mode=0640 tar_time=1519347702.000000000
    85  	..
    86  	..
    87  	# etc/dirB
    88  	dirB type=dir mode=0750 tar_time=1519347702.000000000
    89  	# etc/dirB/dirB1
    90  	dirB1 type=dir mode=0750 tar_time=1519347702.000000000
    91  	..
    92  	..
    93  	# etc/dirtoberemovedandrewritten
    94  	dirtoberemovedandrewritten type=dir mode=0750 tar_time=1519347702.000000000
    95  	..
    96  	..
    97  `
    98  
    99  func WriteTestFileSystem(t *testing.T, testee fs.Writer) (tmpDir string, rootfs string) {
   100  	// Test basic file, directory, link, fifo, block writes
   101  	times := fs.FileTimes{}
   102  	var err error
   103  	times.Mtime, err = time.Parse(time.RFC3339, "2018-02-23T01:01:42Z") // unix: 1519347702
   104  	require.NoError(t, err)
   105  	times.Mtime = time.Unix(times.Mtime.Unix(), 123)
   106  	times.Atime, err = time.Parse(time.RFC3339, "2018-01-23T01:01:43Z")
   107  	require.NoError(t, err)
   108  	dirAttrs := fs.FileAttrs{Mode: os.ModeDir | 0750, UserIds: idutils.UserIds{0, 0}, FileTimes: times}
   109  	dirAttrsDef := dirAttrs
   110  	dirAttrsDef.Mode = os.ModeDir | 0775
   111  	fileAttrsA := fs.NodeAttrs{NodeInfo: fs.NodeInfo{fs.TypeFile, fs.FileAttrs{Mode: 0640, UserIds: idutils.UserIds{0, 0}, Size: 1, FileTimes: times}}}
   112  	fileAttrsB := fs.NodeAttrs{NodeInfo: fs.NodeInfo{fs.TypeFile, fs.FileAttrs{Mode: 0640, UserIds: idutils.UserIds{0, 0}, Size: 3, FileTimes: times}}}
   113  	symlinkAttrs := fs.FileAttrs{Mode: os.ModeSymlink, UserIds: idutils.UserIds{0, 0}, FileTimes: times}
   114  	fifoAttrs := fs.DeviceAttrs{fs.FileAttrs{Mode: os.ModeNamedPipe | 0640, UserIds: idutils.UserIds{0, 0}, FileTimes: times}, 1, 1}
   115  	chardevAttrs := fs.DeviceAttrs{fs.FileAttrs{Mode: os.ModeCharDevice | 0640, UserIds: idutils.UserIds{0, 0}, FileTimes: times}, 1, 1}
   116  	blkAttrs := fs.DeviceAttrs{fs.FileAttrs{Mode: os.ModeDevice | 0640, UserIds: idutils.UserIds{0, 0}, FileTimes: times}, 1, 1}
   117  	fileA := NewSourceMock(fs.TypeFile, fileAttrsA.FileAttrs, "")
   118  	fileA.NodeAttrs = fileAttrsA
   119  	fileA.Readable = fs.NewReadableBytes([]byte("a"))
   120  	fileB := NewSourceMock(fs.TypeFile, fileAttrsB.FileAttrs, "")
   121  	fileB.NodeAttrs = fileAttrsB
   122  	fileB.Readable = fs.NewReadableBytes([]byte("bbb"))
   123  	err = testee.Dir("etc", "", dirAttrsDef)
   124  	require.NoError(t, err)
   125  	err = testee.Dir("/", "", dirAttrsDef)
   126  	require.NoError(t, err)
   127  	reader, err := testee.File("etc/fileA", fileA)
   128  	require.NoError(t, err)
   129  	assert.NotNil(t, reader, "reader returned from File(/etc/fileA)")
   130  	_, err = testee.File("/etc/fileB", fileB)
   131  	require.NoError(t, err)
   132  	err = testee.Dir("etc/dirA", "", dirAttrs)
   133  	require.NoError(t, err)
   134  	err = testee.Dir("/etc/dirB", "", dirAttrs)
   135  	require.NoError(t, err)
   136  	err = testee.Fifo("/etc/fifo", fifoAttrs)
   137  	require.NoError(t, err)
   138  	err = testee.Device("/etc/blockA", blkAttrs)
   139  	require.NoError(t, err)
   140  	err = testee.Device("/etc/chrdevA", chardevAttrs)
   141  	require.NoError(t, err)
   142  	err = testee.Link("/etc/link-abs", "/etc/fileA")
   143  	require.NoError(t, err)
   144  	err = testee.Link("/etc/link-rel", "../etc/fileA")
   145  	require.NoError(t, err)
   146  	err = testee.Dir("dir1", "", dirAttrsDef)
   147  	require.NoError(t, err)
   148  	_, err = testee.File("/dir1/file1", fileA)
   149  	require.NoError(t, err)
   150  	_, err = testee.File("/dir1/file2", fileA)
   151  	require.NoError(t, err)
   152  	err = testee.Dir("/dir2", "", dirAttrsDef)
   153  	require.NoError(t, err)
   154  	_, err = testee.File("/dir2/file3", fileB)
   155  	require.NoError(t, err)
   156  	symlinkAttrs.Symlink = "nesteddir"
   157  	err = testee.Symlink("/dir1/sdir/nestedsymlink", symlinkAttrs)
   158  	require.NoError(t, err)
   159  	err = testee.Dir("/dir1/sdir/nesteddir", "", dirAttrs)
   160  	require.NoError(t, err)
   161  	err = testee.Dir("/dir1/sdir", "", dirAttrsDef)
   162  	require.NoError(t, err)
   163  
   164  	// Test symlink resolution
   165  	symlinkAttrs.Symlink = "../etc/dirA"
   166  	err = testee.Symlink("etc/symlink-rel", symlinkAttrs)
   167  	require.NoError(t, err)
   168  	symlinkAttrs.Symlink = "/etc/dirB"
   169  	err = testee.Symlink("etc/symlink-abs", symlinkAttrs)
   170  	require.NoError(t, err)
   171  	err = testee.Dir("etc/symlink-rel/dirA1", "", dirAttrs)
   172  	require.NoError(t, err)
   173  	err = testee.Dir("etc/symlink-abs/dirB1", "", dirAttrs)
   174  	require.NoError(t, err)
   175  	_, err = testee.File("etc/symlink-rel/symlinkResolved1", fileA)
   176  	require.NoError(t, err)
   177  	_, err = testee.File("/etc/symlink-rel/dirA1/symlinkResolved2", fileA)
   178  	require.NoError(t, err)
   179  
   180  	// Test replacements
   181  	symlinkAttrs.Symlink = "/etc/dirB"
   182  	err = testee.Dir("etc/_dirReplacedWithFile", "", dirAttrs)
   183  	require.NoError(t, err)
   184  	_, err = testee.File("etc/_dirReplacedWithFile", fileA)
   185  	require.NoError(t, err)
   186  	_, err = testee.File("etc/_fileReplacedWithDir", fileA)
   187  	require.NoError(t, err)
   188  	err = testee.Dir("etc/_fileReplacedWithDir", "", dirAttrs)
   189  	require.NoError(t, err)
   190  	err = testee.Symlink("etc/_dirReplacedWithSymlink", symlinkAttrs)
   191  	require.NoError(t, err)
   192  	err = testee.Symlink("etc/_symlinkReplacedWithFile", symlinkAttrs)
   193  	require.NoError(t, err)
   194  	_, err = testee.File("etc/_symlinkReplacedWithFile", fileA)
   195  	require.NoError(t, err)
   196  	err = testee.Symlink("etc/_symlinkReplacedWithDir", symlinkAttrs)
   197  	require.NoError(t, err)
   198  	err = testee.Dir("etc/_symlinkReplacedWithDir", "", dirAttrs)
   199  	require.NoError(t, err)
   200  	err = testee.Link("etc/_linkReplacedWithFile", "/etc/symlink-rel/symlinkResolved1")
   201  	require.NoError(t, err)
   202  	_, err = testee.File("etc/_linkReplacedWithFile", fileA)
   203  	require.NoError(t, err)
   204  
   205  	// Test remove
   206  	err = testee.Dir("etc/dirtoberemoved", "", dirAttrs)
   207  	require.NoError(t, err)
   208  	err = testee.Remove("etc/dirtoberemoved")
   209  	require.NoError(t, err)
   210  	err = testee.Dir("etc/dirA/subdirtoberemoved/nesteddir", "", dirAttrs)
   211  	require.NoError(t, err)
   212  	err = testee.Remove("etc/dirA/subdirtoberemoved")
   213  	require.NoError(t, err)
   214  	_, err = testee.File("etc/filetoberemoved", fileA)
   215  	require.NoError(t, err)
   216  	err = testee.Dir("etc/dirtoberemovedandrewritten", "", dirAttrs)
   217  	require.NoError(t, err)
   218  	err = testee.Remove("etc/dirtoberemovedandrewritten")
   219  	require.NoError(t, err)
   220  	err = testee.Dir("etc/dirtoberemovedandrewritten", "", dirAttrs)
   221  	require.NoError(t, err)
   222  	err = testee.Remove("etc/filetoberemoved")
   223  	require.NoError(t, err)
   224  	_, err = testee.File("etc/symlink-rel/dirA1/filetoberemoved", fileA)
   225  	require.NoError(t, err)
   226  	err = testee.Remove("etc/symlink-rel/dirA1/filetoberemoved")
   227  	require.NoError(t, err)
   228  	err = testee.Symlink("etc/symlink-toberemoved", symlinkAttrs)
   229  	require.NoError(t, err)
   230  	err = testee.Remove("etc/symlink-toberemoved")
   231  	require.NoError(t, err)
   232  	err = testee.Symlink("etc/link-toberemoved", symlinkAttrs)
   233  	require.NoError(t, err)
   234  	err = testee.Remove("etc/link-toberemoved")
   235  	require.NoError(t, err)
   236  
   237  	// Test file system boundaries
   238  	_, err = testee.File("/../filepathsanitized", fileA)
   239  	require.NoError(t, err)
   240  
   241  	symlinkAttrs.Symlink = filepath.Join("..", "..")
   242  	err = testee.Symlink("etc/symlink-sanitized", symlinkAttrs)
   243  	if err != nil {
   244  		t.Errorf("should not return error when symlinking outside rootfs boundaries (/..): %s", err)
   245  		t.FailNow()
   246  	}
   247  	return
   248  }
   249  
   250  func AssertFsState(t *testing.T, rootfs, rootPath string, keywords []mtree.Keyword, expectedDirectoryHierarchy string) {
   251  	expectedDirectoryHierarchy = strings.Replace(expectedDirectoryHierarchy, "$ROOT", rootPath, -1)
   252  	expectedDh, err := mtree.ParseSpec(strings.NewReader(expectedDirectoryHierarchy))
   253  	require.NoError(t, err)
   254  	dh, err := mtree.Walk(rootfs, nil, keywords, fseval.DefaultFsEval)
   255  	require.NoError(t, err)
   256  	diff, err := mtree.Compare(expectedDh, dh, keywords)
   257  	require.NoError(t, err)
   258  	// TODO: do not exclude device files from test but map mocked device files properly back in rootless mode
   259  	if len(diff) > 0 /* && (diff[0].Path() != "etc/blockA" || diff[0].Type() != mtree.Modified)*/ {
   260  		var buf bytes.Buffer
   261  		_, err = dh.WriteTo(&buf)
   262  		require.NoError(t, err)
   263  		fmt.Println(string(buf.Bytes()))
   264  		s := make([]string, len(diff))
   265  		for i, c := range diff {
   266  			s[i] = c.String()
   267  		}
   268  		sort.Strings(s)
   269  		t.Error("Unexpected rootfs differences:\n  " + strings.Join(s, "\n  "))
   270  		t.Fail()
   271  	}
   272  }