github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/testutils/tar.go (about)

     1  package testutils
     2  
     3  import (
     4  	"archive/tar"
     5  	"bytes"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"runtime"
    10  	"testing"
    11  
    12  	"github.com/stretchr/testify/assert"
    13  )
    14  
    15  const expectedUidAndGid = 0
    16  
    17  type ExpectedFile struct {
    18  	Path     string
    19  	Contents string
    20  
    21  	// If true, we will assert that the file is not in the tarball.
    22  	Missing bool
    23  
    24  	// If true, we will assert the file is a dir.
    25  	IsDir bool
    26  
    27  	// If true, we will assert that UID and GID are 0
    28  	AssertUidAndGidAreZero bool
    29  
    30  	// If true, we will assert that this is a symlink with a linkname.
    31  	Linkname string
    32  
    33  	// If non-zero, assert that file permission and mode bits match this value
    34  	Mode int64
    35  
    36  	// Windows filesystem APIs don't expose an executable bit.
    37  	// So when we create docker images on windows, we set the executable
    38  	// bit on all files, for consistency with Docker.
    39  	HasExecBitWindows bool
    40  }
    41  
    42  // Asserts whether or not this file is in the tar.
    43  func AssertFileInTar(t testing.TB, tr *tar.Reader, expected ExpectedFile) {
    44  	AssertFilesInTar(t, tr, []ExpectedFile{expected})
    45  }
    46  
    47  // Asserts whether or not these files are in the tar, but not that they are the only
    48  // files in the tarball.
    49  func AssertFilesInTar(t testing.TB, tr *tar.Reader, expectedFiles []ExpectedFile,
    50  	msgAndArgs ...interface{}) {
    51  	msg := "AssertFilesInTar"
    52  	if len(msgAndArgs) > 0 {
    53  		m := msgAndArgs[0]
    54  		s, ok := m.(string)
    55  		if !ok {
    56  			t.Fatalf("first arg to msgAndArgs (%v) not string", m)
    57  		}
    58  		msg = fmt.Sprintf(s, msgAndArgs[1:]...)
    59  	}
    60  	dupes := make(map[string]bool)
    61  
    62  	burndownMap := make(map[string]ExpectedFile, len(expectedFiles))
    63  	for _, f := range expectedFiles {
    64  		burndownMap[f.Path] = f
    65  	}
    66  
    67  	for {
    68  		header, err := tr.Next()
    69  		if err == io.EOF {
    70  			break
    71  		} else if err != nil {
    72  			t.Fatalf("Error reading tar file: %v (%s)", err, msg)
    73  		}
    74  
    75  		if dupes[header.Name] {
    76  			t.Fatalf("File in tarball twice. This is invalid and will break when extracted: %v (%s)", header.Name, msg)
    77  		}
    78  
    79  		dupes[header.Name] = true
    80  
    81  		expected, ok := burndownMap[header.Name]
    82  		if !ok {
    83  			continue
    84  		}
    85  
    86  		// we found it!
    87  		delete(burndownMap, expected.Path)
    88  
    89  		if expected.Missing {
    90  			t.Errorf("Path %q was not expected in the tarball (%s)", expected.Path, msg)
    91  			continue
    92  		}
    93  
    94  		expectedReg := !expected.IsDir && expected.Linkname == ""
    95  		expectedDir := expected.IsDir
    96  		expectedSymlink := expected.Linkname != ""
    97  		if expectedReg && header.Typeflag != tar.TypeReg {
    98  			t.Errorf("Path %q exists but is not a regular file (%s)", expected.Path, msg)
    99  			continue
   100  		}
   101  
   102  		if expectedDir && header.Typeflag != tar.TypeDir {
   103  			t.Errorf("Path %q exists but is not a directory (%s)", expected.Path, msg)
   104  			continue
   105  		}
   106  
   107  		if expectedSymlink && header.Typeflag != tar.TypeSymlink {
   108  			t.Errorf("Path %q exists but is not a directory (%s)", expected.Path, msg)
   109  			continue
   110  		}
   111  
   112  		if expected.AssertUidAndGidAreZero && header.Uid != expectedUidAndGid {
   113  			t.Errorf("Expected %s to have UID 0, got %d (%s)", header.Name, header.Uid, msg)
   114  		}
   115  
   116  		if expected.AssertUidAndGidAreZero && header.Gid != expectedUidAndGid {
   117  			t.Errorf("Expected %s to have GID 0, got %d (%s)", header.Name, header.Gid, msg)
   118  		}
   119  
   120  		if expected.Mode != 0 && header.Mode != expected.Mode {
   121  			t.Errorf("Expected %s to have mode %d, got %d", header.Name, expected.Mode, header.Mode)
   122  		}
   123  
   124  		if expected.HasExecBitWindows && runtime.GOOS == "windows" && (os.FileMode(header.Mode)&0111 == 0) {
   125  			t.Errorf("Expected %s to have Executable bit, got %s", header.Name, os.FileMode(header.Mode))
   126  		}
   127  
   128  		if header.Linkname != expected.Linkname {
   129  			t.Errorf("Expected linkname %q, actual %q (%s)", expected.Linkname, header.Linkname, msg)
   130  		}
   131  
   132  		if expectedReg {
   133  			contents := bytes.NewBuffer(nil)
   134  			_, err = io.Copy(contents, tr)
   135  			if err != nil {
   136  				t.Fatalf("Error reading tar file: %v (%s)", err, msg)
   137  			}
   138  
   139  			if !assert.Equal(t, expected.Contents, contents.String()) {
   140  				fmt.Printf("wrong contents in %q\n (%s)", expected.Path, msg)
   141  				continue
   142  			}
   143  		}
   144  
   145  		continue
   146  	}
   147  
   148  	for _, f := range burndownMap {
   149  		if !f.Missing {
   150  			t.Errorf("File not found in container: %s (%s)", f.Path, msg)
   151  		}
   152  	}
   153  }