lifecycle/layers/dir_test.go

200 lines
5.7 KiB
Go

package layers_test
import (
"archive/tar"
"fmt"
"io"
"os"
"path/filepath"
"testing"
"time"
"github.com/apex/log"
"github.com/apex/log/handlers/memory"
"github.com/sclevine/spec"
"github.com/sclevine/spec/report"
"github.com/buildpacks/lifecycle/layers"
h "github.com/buildpacks/lifecycle/testhelpers"
)
func TestDirLayers(t *testing.T) {
spec.Run(t, "Factory", testDirs, spec.Parallel(), spec.Report(report.Terminal{}))
}
func testDirs(t *testing.T, when spec.G, it spec.S) {
var (
factory *layers.Factory
dir string
logHandler = memory.New()
)
it.Before(func() {
var err error
artifactDir, err := os.MkdirTemp("", "layers.slices.layer")
h.AssertNil(t, err)
factory = &layers.Factory{
ArtifactsDir: artifactDir,
Logger: &log.Logger{Handler: logHandler},
UID: 1234,
GID: 4321,
}
dir, err = filepath.Abs(filepath.Join("testdata", "target-dir"))
h.AssertNil(t, err)
})
it.After(func() {
h.AssertNil(t, os.RemoveAll(factory.ArtifactsDir))
})
when("#DirLayer", func() {
var dirLayer layers.Layer
it.Before(func() {
var err error
dirLayer, err = factory.DirLayer("some-layer-id", dir, "some-created-by")
h.AssertNil(t, err)
})
it("creates a layer from the directory", func() {
// parent layers should have uid/gid matching the filesystem
// the dir and it's children should have normalized uid/gid
h.AssertEq(t, dirLayer.ID, "some-layer-id")
assertTarEntries(t, dirLayer.TarPath, append(parents(t, dir), []*tar.Header{
{
Name: tarPath(dir),
Uid: factory.UID,
Gid: factory.GID,
Typeflag: tar.TypeDir,
},
{
Name: tarPath(filepath.Join(dir, "dir-link")),
Uid: factory.UID,
Gid: factory.GID,
Typeflag: tar.TypeSymlink,
},
{
Name: tarPath(filepath.Join(dir, "file-link.txt")),
Uid: factory.UID,
Gid: factory.GID,
Typeflag: tar.TypeSymlink,
},
{
Name: tarPath(filepath.Join(dir, "file.txt")),
Uid: factory.UID,
Gid: factory.GID,
Typeflag: tar.TypeReg,
},
{
Name: tarPath(filepath.Join(dir, "other-dir")),
Uid: factory.UID,
Gid: factory.GID,
Typeflag: tar.TypeDir,
},
{
Name: tarPath(filepath.Join(dir, "other-dir", "other-file.md")),
Uid: factory.UID,
Gid: factory.GID,
Typeflag: tar.TypeReg,
},
{
Name: tarPath(filepath.Join(dir, "other-dir", "other-file.txt")),
Uid: factory.UID,
Gid: factory.GID,
Typeflag: tar.TypeReg,
},
{
Name: tarPath(filepath.Join(dir, "some-dir")),
Uid: factory.UID,
Gid: factory.GID,
Typeflag: tar.TypeDir,
},
{
Name: tarPath(filepath.Join(dir, "some-dir", "file.md")),
Uid: factory.UID,
Gid: factory.GID,
Typeflag: tar.TypeReg,
},
{
Name: tarPath(filepath.Join(dir, "some-dir", "some-file.txt")),
Uid: factory.UID,
Gid: factory.GID,
Typeflag: tar.TypeReg,
},
}...))
// it returns history
h.AssertEq(t, dirLayer.History.CreatedBy, "some-created-by")
})
it("reuses tars when possible", func() {
layerTar, err := os.Stat(dirLayer.TarPath)
h.AssertNil(t, err)
modTime := layerTar.ModTime()
reusedDirLayer, err := factory.DirLayer("some-layer-id", dir, "some-created-by")
h.AssertNil(t, err)
h.AssertEq(t, reusedDirLayer, dirLayer)
layerTar, err = os.Stat(reusedDirLayer.TarPath)
h.AssertNil(t, err)
h.AssertEq(t, modTime, layerTar.ModTime()) // assert file has not been modified
h.AssertEq(t, len(logHandler.Entries), 1)
h.AssertEq(t,
logHandler.Entries[0].Message,
fmt.Sprintf("Reusing tarball for layer \"some-layer-id\" with SHA: %s\n", dirLayer.Digest),
)
})
})
}
func assertTarEntries(t *testing.T, tarPath string, expectedEntries []*tar.Header) {
t.Helper()
lf, err := os.Open(tarPath)
h.AssertNil(t, err)
defer lf.Close()
tr := tar.NewReader(lf)
assertOSSpecificEntries(t, tr)
var allEntryNames []string
for i, expected := range expectedEntries {
header, err := tr.Next()
if err == io.EOF {
t.Fatalf("missing expected archive entry '%s'\n archive contained %v", expected.Name, allEntryNames)
}
h.AssertNil(t, err)
allEntryNames = append(allEntryNames, header.Name)
if header.Name != expected.Name {
t.Fatalf("expected entry '%d' to have name %q, got %q", i, expected.Name, header.Name)
}
if header.Typeflag != expected.Typeflag {
t.Fatalf("expected entry '%s' to have type %q, got %q", expected.Name, expected.Typeflag, header.Typeflag)
}
if expected.Mode != 0 && header.Mode != expected.Mode { // FIXME: add modes to all expects to remove the 0 hack
t.Fatalf("expected entry '%s' to have mode %d, got %d", expected.Name, expected.Mode, header.Mode)
}
assertOSSpecificFields(t, expected, header)
if !header.ModTime.Equal(time.Date(1980, time.January, 1, 0, 0, 1, 0, time.UTC)) {
t.Fatalf("expected entry '%s' to normalized mod time, got '%s", expected.Name, header.ModTime)
}
if header.Uname != "" {
t.Fatalf("expected entry '%s' to empty Uname, got '%s", expected.Name, header.Uname)
}
if header.Gname != "" {
t.Fatalf("expected entry '%s' to empty Gname, got '%s", expected.Name, header.Gname)
}
h.AssertEq(t, header.Typeflag, expected.Typeflag)
}
header, err := tr.Next()
if err != io.EOF {
t.Fatalf("unexpected archive entry '%s'", header.Name)
}
}
func parents(t *testing.T, file string) []*tar.Header {
t.Helper()
parent := filepath.Dir(file)
if parent == `/` || parent == filepath.VolumeName(file)+`\` {
return []*tar.Header{}
}
fi, err := os.Stat(parent)
h.AssertNil(t, err)
return append(parents(t, parent), parentHeader(parent, fi))
}