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

     1  package build
     2  
     3  import (
     4  	"archive/tar"
     5  	"bytes"
     6  	"context"
     7  	"io"
     8  	"net"
     9  	"os"
    10  	"runtime"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  
    17  	"github.com/tilt-dev/tilt/internal/dockerfile"
    18  	"github.com/tilt-dev/tilt/internal/dockerignore"
    19  	"github.com/tilt-dev/tilt/internal/testutils"
    20  	"github.com/tilt-dev/tilt/internal/testutils/tempdir"
    21  	"github.com/tilt-dev/tilt/pkg/model"
    22  )
    23  
    24  func TestArchiveDf(t *testing.T) {
    25  	f := newFixture(t)
    26  
    27  	dfText := "FROM alpine"
    28  	buf := new(bytes.Buffer)
    29  	ab := NewArchiveBuilder(buf, model.EmptyMatcher)
    30  	defer ab.Close()
    31  
    32  	df := dockerfile.Dockerfile(dfText)
    33  	err := ab.archiveDf(f.ctx, df)
    34  	if err != nil {
    35  		panic(err)
    36  	}
    37  
    38  	actual := tar.NewReader(buf)
    39  
    40  	f.assertFileInTar(actual, expectedFile{
    41  		Path:                   "Dockerfile",
    42  		Contents:               dfText,
    43  		AssertUidAndGidAreZero: true,
    44  		Mode:                   0644,
    45  	})
    46  }
    47  
    48  func TestArchivePathsIfExists(t *testing.T) {
    49  	f := newFixture(t)
    50  	pr, pw := io.Pipe()
    51  	go func() {
    52  		ab := NewArchiveBuilder(pw, model.EmptyMatcher)
    53  		defer ab.Close()
    54  
    55  		f.WriteFile("a", "a")
    56  
    57  		paths := []PathMapping{
    58  			PathMapping{
    59  				LocalPath:     f.JoinPath("a"),
    60  				ContainerPath: "/a",
    61  			},
    62  			PathMapping{
    63  				LocalPath:     f.JoinPath("b"),
    64  				ContainerPath: "/b",
    65  			},
    66  		}
    67  
    68  		err := ab.ArchivePathsIfExist(f.ctx, paths)
    69  		require.NoError(t, err)
    70  		assert.Equal(t, ab.Paths(), []string{f.JoinPath("a")})
    71  	}()
    72  
    73  	actual := tar.NewReader(pr)
    74  	f.assertFilesInTar(actual, []expectedFile{
    75  		expectedFile{Path: "a", Contents: "a", AssertUidAndGidAreZero: true, HasExecBitWindows: true},
    76  		expectedFile{Path: "b", Missing: true},
    77  	})
    78  }
    79  
    80  func TestDontArchiveTiltfile(t *testing.T) {
    81  	f := newFixture(t)
    82  
    83  	filter, err := dockerignore.NewDockerPatternMatcher(f.Path(), []string{"Tiltfile"})
    84  	if err != nil {
    85  		t.Fatal(err)
    86  	}
    87  
    88  	buf := new(bytes.Buffer)
    89  	ab := NewArchiveBuilder(buf, filter)
    90  	defer ab.Close()
    91  
    92  	f.WriteFile("a", "a")
    93  	f.WriteFile("Tiltfile", "Tiltfile")
    94  
    95  	paths := []PathMapping{
    96  		PathMapping{
    97  			LocalPath:     f.JoinPath("a"),
    98  			ContainerPath: "/a",
    99  		},
   100  		PathMapping{
   101  			LocalPath:     f.JoinPath("Tiltfile"),
   102  			ContainerPath: "/Tiltfile",
   103  		},
   104  	}
   105  
   106  	err = ab.ArchivePathsIfExist(f.ctx, paths)
   107  	if err != nil {
   108  		f.t.Fatal(err)
   109  	}
   110  
   111  	actual := tar.NewReader(buf)
   112  
   113  	testutils.AssertFilesInTar(
   114  		t,
   115  		actual,
   116  		[]testutils.ExpectedFile{
   117  			testutils.ExpectedFile{
   118  				Path:     "a",
   119  				Contents: "a",
   120  			},
   121  			testutils.ExpectedFile{
   122  				Path:    "Tiltfile",
   123  				Missing: true,
   124  			},
   125  		},
   126  	)
   127  }
   128  
   129  func TestArchiveOverlapping(t *testing.T) {
   130  	if runtime.GOOS == "windows" {
   131  		t.Skip("Cannot create a symlink on windows")
   132  	}
   133  
   134  	f := newFixture(t)
   135  	buf := new(bytes.Buffer)
   136  	ab := NewArchiveBuilder(buf, model.EmptyMatcher)
   137  	defer ab.Close()
   138  
   139  	f.WriteFile("a/a.txt", "a.txt contents")
   140  	f.WriteFile("b/b.txt", "b.txt contents")
   141  	f.WriteSymlink("../b", "a/b")
   142  
   143  	paths := []PathMapping{
   144  		PathMapping{
   145  			LocalPath:     f.JoinPath("a"),
   146  			ContainerPath: "/a",
   147  		},
   148  		PathMapping{
   149  			LocalPath:     f.JoinPath("b"),
   150  			ContainerPath: "/a/b",
   151  		},
   152  	}
   153  
   154  	err := ab.ArchivePathsIfExist(f.ctx, paths)
   155  	if err != nil {
   156  		f.t.Fatal(err)
   157  	}
   158  
   159  	actual := tar.NewReader(buf)
   160  	f.assertFilesInTar(actual, []expectedFile{
   161  		expectedFile{Path: "a/a.txt", Contents: "a.txt contents", AssertUidAndGidAreZero: true},
   162  		expectedFile{Path: "a/b", IsDir: true},
   163  		expectedFile{Path: "a/b/b.txt", Contents: "b.txt contents"},
   164  	})
   165  }
   166  
   167  func TestArchiveSymlink(t *testing.T) {
   168  	if runtime.GOOS == "windows" {
   169  		t.Skip("Cannot create a symlink on windows")
   170  	}
   171  
   172  	f := newFixture(t)
   173  	buf := new(bytes.Buffer)
   174  	ab := NewArchiveBuilder(buf, model.EmptyMatcher)
   175  	defer ab.Close()
   176  
   177  	f.WriteFile("src/a.txt", "hello world")
   178  	f.WriteSymlink("a.txt", "src/b.txt")
   179  
   180  	paths := []PathMapping{
   181  		PathMapping{
   182  			LocalPath:     f.JoinPath("src"),
   183  			ContainerPath: "/src",
   184  		},
   185  	}
   186  
   187  	err := ab.ArchivePathsIfExist(f.ctx, paths)
   188  	if err != nil {
   189  		f.t.Fatal(err)
   190  	}
   191  
   192  	actual := tar.NewReader(buf)
   193  	f.assertFilesInTar(actual, []expectedFile{
   194  		expectedFile{Path: "src/a.txt", Contents: "hello world"},
   195  		expectedFile{Path: "src/b.txt", Linkname: "a.txt"},
   196  	})
   197  }
   198  
   199  func TestArchiveSocket(t *testing.T) {
   200  	if runtime.GOOS == "windows" {
   201  		t.Skip("Cannot create a unix socket on windows")
   202  	}
   203  
   204  	f := newFixture(t)
   205  	buf := new(bytes.Buffer)
   206  	ab := NewArchiveBuilder(buf, model.EmptyMatcher)
   207  	defer ab.Close()
   208  
   209  	f.WriteFile("src/a.txt", "hello world")
   210  	c, err := net.Listen("unix", f.JoinPath("src/my.sock"))
   211  	if err != nil {
   212  		t.Fatal(err)
   213  	}
   214  	defer c.Close()
   215  
   216  	paths := []PathMapping{
   217  		PathMapping{
   218  			LocalPath:     f.JoinPath("src"),
   219  			ContainerPath: "/src",
   220  		},
   221  	}
   222  
   223  	err = ab.ArchivePathsIfExist(f.ctx, paths)
   224  	if err != nil {
   225  		f.t.Fatal(err)
   226  	}
   227  
   228  	actual := tar.NewReader(buf)
   229  	f.assertFilesInTar(actual, []expectedFile{
   230  		expectedFile{Path: "src/a.txt", Contents: "hello world"},
   231  		expectedFile{Path: "src/my.sock", Missing: true},
   232  	})
   233  }
   234  
   235  func TestArchiveException(t *testing.T) {
   236  	f := newFixture(t)
   237  
   238  	filter, err := dockerignore.NewDockerPatternMatcher(f.Path(), []string{"*", "!target"})
   239  	if err != nil {
   240  		t.Fatal(err)
   241  	}
   242  
   243  	buf := new(bytes.Buffer)
   244  	ab := NewArchiveBuilder(buf, filter)
   245  	defer ab.Close()
   246  
   247  	f.WriteFile("target/foo.txt", "bar")
   248  
   249  	paths := []PathMapping{{LocalPath: f.Path(), ContainerPath: "/"}}
   250  
   251  	err = ab.ArchivePathsIfExist(f.ctx, paths)
   252  	if err != nil {
   253  		f.t.Fatal(err)
   254  	}
   255  
   256  	actual := tar.NewReader(buf)
   257  	f.assertFileInTar(actual, expectedFile{Path: "target/foo.txt", Contents: "bar"})
   258  }
   259  
   260  func TestArchiveAllGoFiles(t *testing.T) {
   261  	f := newFixture(t)
   262  
   263  	filter, err := dockerignore.NewDockerPatternMatcher(f.Path(),
   264  		[]string{"*", "!pkg/**/*.go"})
   265  	require.NoError(t, err)
   266  
   267  	buf := new(bytes.Buffer)
   268  	ab := NewArchiveBuilder(buf, filter)
   269  	defer ab.Close()
   270  
   271  	f.WriteFile("pkg/internal/somemodule/foo.go", "bar")
   272  
   273  	paths := []PathMapping{{LocalPath: f.Path(), ContainerPath: "/"}}
   274  
   275  	err = ab.ArchivePathsIfExist(f.ctx, paths)
   276  	if err != nil {
   277  		f.t.Fatal(err)
   278  	}
   279  
   280  	actual := tar.NewReader(buf)
   281  	f.assertFileInTar(actual,
   282  		expectedFile{Path: "pkg/internal/somemodule/foo.go", Contents: "bar"})
   283  }
   284  
   285  // Write a file continuously, and make sure we don't get tar errors.
   286  func TestRapidWrite(t *testing.T) {
   287  	f := newFixture(t)
   288  
   289  	f.WriteFile("log.txt", "a")
   290  
   291  	errCh := make(chan error)
   292  	ctx, cancel := context.WithCancel(f.ctx)
   293  	defer cancel()
   294  
   295  	// Continuously open the file for writing.
   296  	go func() {
   297  		defer close(errCh)
   298  
   299  		for {
   300  			if ctx.Err() != nil {
   301  				return
   302  			}
   303  
   304  			path := f.JoinPath("log.txt")
   305  			f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0755)
   306  			if err != nil {
   307  				errCh <- err
   308  				return
   309  			}
   310  
   311  			_, err = f.Write([]byte("a\n"))
   312  			_ = f.Close()
   313  			if err != nil {
   314  				errCh <- err
   315  				return
   316  			}
   317  			time.Sleep(100 * time.Microsecond)
   318  		}
   319  	}()
   320  
   321  	paths := []PathMapping{
   322  		PathMapping{
   323  			LocalPath:     f.JoinPath("log.txt"),
   324  			ContainerPath: "/a.txt",
   325  		},
   326  	}
   327  
   328  	// Archive the file 10 times and make sure it's a success.
   329  	for i := 0; i < 10; i++ {
   330  		buf := new(bytes.Buffer)
   331  		ab := NewArchiveBuilder(buf, model.EmptyMatcher)
   332  		err := ab.ArchivePathsIfExist(f.ctx, paths)
   333  		require.NoError(t, err)
   334  		assert.Equal(t, ab.Paths(), []string{f.JoinPath("log.txt")})
   335  		require.NoError(t, ab.Close())
   336  		time.Sleep(100 * time.Microsecond)
   337  	}
   338  	cancel()
   339  	assert.NoError(t, <-errCh)
   340  }
   341  
   342  type fixture struct {
   343  	*tempdir.TempDirFixture
   344  	t   *testing.T
   345  	ctx context.Context
   346  }
   347  
   348  func newFixture(t *testing.T) *fixture {
   349  	ctx, _, _ := testutils.CtxAndAnalyticsForTest()
   350  
   351  	return &fixture{
   352  		TempDirFixture: tempdir.NewTempDirFixture(t),
   353  		t:              t,
   354  		ctx:            ctx,
   355  	}
   356  }
   357  
   358  func (f *fixture) assertFileInTar(tr *tar.Reader, expected expectedFile) {
   359  	testutils.AssertFileInTar(f.t, tr, expected)
   360  }
   361  
   362  func (f *fixture) assertFilesInTar(tr *tar.Reader, expected []expectedFile) {
   363  	testutils.AssertFilesInTar(f.t, tr, expected)
   364  }