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 }