blob: ce882f3ffdd6d47821323712ef937b14abd5589d [file] [log] [blame]
package filemetadata
import (
"fmt"
"os"
"path/filepath"
"testing"
"time"
"github.com/bazelbuild/remote-apis-sdks/go/pkg/digest"
"github.com/bazelbuild/remote-apis-sdks/go/pkg/testutil"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
)
var (
ignoreMtime = cmpopts.IgnoreFields(Metadata{}, "MTime")
)
func TestComputeFilesNoXattr(t *testing.T) {
tests := []struct {
name string
contents string
executable bool
}{
{
name: "empty",
contents: "",
},
{
name: "non-executable",
contents: "bla",
},
{
name: "executable",
contents: "foo",
executable: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
before := time.Now().Truncate(time.Second)
time.Sleep(5 * time.Second)
filename, err := testutil.CreateFile(t, tc.executable, tc.contents)
if err != nil {
t.Fatalf("Failed to create tmp file for testing digests: %v", err)
}
after := time.Now().Truncate(time.Second).Add(time.Second)
defer os.RemoveAll(filename)
got := Compute(filename)
if got.Err != nil {
t.Errorf("Compute(%v) failed. Got error: %v", filename, got.Err)
}
want := &Metadata{
Digest: digest.NewFromBlob([]byte(tc.contents)),
IsExecutable: tc.executable,
}
if diff := cmp.Diff(want, got, ignoreMtime); diff != "" {
t.Errorf("Compute(%v) returned diff. (-want +got)\n%s", filename, diff)
}
if got.MTime.Before(before) || got.MTime.After(after) {
t.Errorf("Compute(%v) returned MTime %v, want time in (%v, %v).", filename, got.MTime, before, after)
}
})
}
}
func TestComputeFilesWithXattr(t *testing.T) {
overwriteXattrGlobals(t, "google.digest.sha256", xattributeAccessorMock{})
tests := []struct {
name string
contents string
executable bool
}{
{
name: "empty",
contents: "",
},
{
name: "non-executable",
contents: "bla",
},
{
name: "executable",
contents: "foo",
executable: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
getXAttrMock = func(_ string, _ string) ([]byte, error) {
return []byte(tc.name), nil
}
before := time.Now().Truncate(time.Second)
filename, err := testutil.CreateFile(t, tc.executable, tc.contents)
if err != nil {
t.Fatalf("Failed to create tmp file for testing digests: %v", err)
}
after := time.Now().Truncate(time.Second).Add(time.Second)
defer os.RemoveAll(filename)
got := Compute(filename)
if got.Err != nil {
t.Errorf("Compute(%v) failed. Got error: %v", filename, got.Err)
}
wantDigest, _ := digest.NewFromString(fmt.Sprintf("%s/%d", tc.name, len(tc.contents)))
want := &Metadata{
Digest: wantDigest,
IsExecutable: tc.executable,
}
if diff := cmp.Diff(want, got, ignoreMtime); diff != "" {
t.Errorf("Compute(%v) returned diff. (-want +got)\n%s", filename, diff)
}
if got.MTime.Before(before) || got.MTime.After(after) {
t.Errorf("Compute(%v) returned MTime %v, want time in (%v, %v).", filename, got.MTime, before, after)
}
})
}
}
func TestComputeDirectory(t *testing.T) {
tmpDir := t.TempDir()
got := Compute(tmpDir)
if got.Err != nil {
t.Errorf("Compute(%v).Err = %v, expected nil", tmpDir, got.Err)
}
if !got.IsDirectory {
t.Errorf("Compute(%v).IsDirectory = false, want true", tmpDir)
}
if got.Digest != digest.Empty {
t.Errorf("Compute(%v).Digest = %v, want %v", tmpDir, got.Digest, digest.Empty)
}
}
func TestComputeSymlinksToFile(t *testing.T) {
tests := []struct {
name string
contents string
executable bool
}{
{
name: "valid",
contents: "bla",
},
{
name: "valid-executable",
contents: "executable",
executable: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
symlinkPath := filepath.Join(os.TempDir(), tc.name)
defer os.RemoveAll(symlinkPath)
targetPath, err := createSymlinkToFile(t, symlinkPath, tc.executable, tc.contents)
if err != nil {
t.Fatalf("Failed to create tmp symlink for testing digests: %v", err)
}
got := Compute(symlinkPath)
if got.Err != nil {
t.Errorf("Compute(%v) failed. Got error: %v", symlinkPath, got.Err)
}
want := &Metadata{
Symlink: &SymlinkMetadata{
Target: targetPath,
IsDangling: false,
},
Digest: digest.NewFromBlob([]byte(tc.contents)),
IsExecutable: tc.executable,
}
if diff := cmp.Diff(want, got, ignoreMtime); diff != "" {
t.Errorf("Compute(%v) returned diff. (-want +got)\n%s", symlinkPath, diff)
}
})
}
}
func TestComputeDanglingSymlinks(t *testing.T) {
// Create a temporary fake target so that os.Symlink() can work.
symlinkPath := filepath.Join(os.TempDir(), "dangling")
defer os.RemoveAll(symlinkPath)
targetPath, err := createSymlinkToFile(t, symlinkPath, false, "transient")
if err != nil {
t.Fatalf("Failed to create tmp symlink for testing digests: %v", err)
}
// Make the symlink dangling
os.RemoveAll(targetPath)
got := Compute(symlinkPath)
if got.Err == nil || !got.Symlink.IsDangling {
t.Errorf("Compute(%v) should fail because the symlink is dangling", symlinkPath)
}
if got.Symlink.Target != targetPath {
t.Errorf("Compute(%v) should still set Target for the dangling symlink, want=%v got=%v", symlinkPath, targetPath, got.Symlink.Target)
}
}
func TestComputeSymlinksToDirectory(t *testing.T) {
symlinkPath := filepath.Join(os.TempDir(), "dir-symlink")
defer os.RemoveAll(symlinkPath)
targetPath := t.TempDir()
if err := createSymlinkToTarget(t, symlinkPath, targetPath); err != nil {
t.Fatalf("Failed to create tmp symlink for testing digests: %v", err)
}
got := Compute(symlinkPath)
if got.Err != nil {
t.Errorf("Compute(%v).Err = %v, expected nil", symlinkPath, got.Err)
}
if !got.IsDirectory {
t.Errorf("Compute(%v).IsDirectory = false, want true", symlinkPath)
}
}
func createSymlinkToFile(t *testing.T, symlinkPath string, executable bool, contents string) (string, error) {
t.Helper()
targetPath, err := testutil.CreateFile(t, executable, contents)
if err != nil {
return "", err
}
if err := createSymlinkToTarget(t, symlinkPath, targetPath); err != nil {
return "", err
}
return targetPath, nil
}
func createSymlinkToTarget(t *testing.T, symlinkPath string, targetPath string) error {
t.Helper()
return os.Symlink(targetPath, symlinkPath)
}
func overwriteXattrGlobals(t *testing.T, newXattrDigestName string, newXattrAccess xattributeAccessorInterface) {
t.Helper()
oldXattrDigestName := XattrDigestName
oldXattrAccess := XattrAccess
XattrDigestName = newXattrDigestName
XattrAccess = newXattrAccess
t.Cleanup(func() {
XattrDigestName = oldXattrDigestName
XattrAccess = oldXattrAccess
})
}
// Mocking of the xattr package for testing.
var getXAttrMock func(path string, name string) ([]byte, error)
type xattributeAccessorMock struct{}
func (x xattributeAccessorMock) getXAttr(path string, name string) ([]byte, error) {
return getXAttrMock(path, name)
}
func (x xattributeAccessorMock) isSupported() bool {
return true
}