mirror of https://github.com/knative/func.git
492 lines
9.5 KiB
Go
492 lines
9.5 KiB
Go
package filesystem_test
|
|
|
|
import (
|
|
"archive/zip"
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"runtime"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/go-git/go-billy/v5/memfs"
|
|
"github.com/go-git/go-git/v5"
|
|
"github.com/go-git/go-git/v5/plumbing/object"
|
|
"github.com/go-git/go-git/v5/storage/memory"
|
|
"github.com/google/go-cmp/cmp"
|
|
|
|
"knative.dev/func/pkg/filesystem"
|
|
fn "knative.dev/func/pkg/functions"
|
|
)
|
|
|
|
const templatesPath = "../../templates"
|
|
|
|
type FileInfo struct {
|
|
Path string
|
|
Typ fs.FileMode
|
|
Executable bool
|
|
Content []byte
|
|
}
|
|
|
|
func TestFileSystems(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
name string
|
|
fileSystem filesystem.Filesystem
|
|
}{
|
|
{
|
|
name: "embedded",
|
|
fileSystem: fn.EmbeddedTemplatesFS,
|
|
},
|
|
{
|
|
name: "os",
|
|
fileSystem: initOSFS(t),
|
|
},
|
|
{
|
|
name: "git",
|
|
fileSystem: initGitFS(t),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
templatesFS := tt.fileSystem
|
|
|
|
if templatesFS == nil && runtime.GOOS == "windows" {
|
|
t.Skip("FS == nil")
|
|
// TODO I have no idea why it returns nil on Windows
|
|
}
|
|
|
|
embeddedFiles, err := loadFS(templatesFS)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
localFiles, err := loadLocalFiles(templatesPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
compare := func(fis []FileInfo) func(i, j int) bool {
|
|
return func(i, j int) bool {
|
|
return fis[i].Path < fis[j].Path
|
|
}
|
|
}
|
|
sort.Slice(embeddedFiles, compare(embeddedFiles))
|
|
sort.Slice(localFiles, compare(localFiles))
|
|
|
|
if diff := cmp.Diff(localFiles, embeddedFiles); diff != "" {
|
|
t.Error("filesystem content missmatch (-want, +got) zz_filesystem_generated.go may need to be regenerated (run make):", diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func loadFS(fileSys filesystem.Filesystem) ([]FileInfo, error) {
|
|
var err error
|
|
var files []FileInfo
|
|
|
|
permMask := fs.FileMode(0111)
|
|
if runtime.GOOS == "windows" {
|
|
permMask = 0
|
|
}
|
|
|
|
err = fs.WalkDir(fileSys, ".", func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fi, err := fileSys.Stat(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var bs []byte
|
|
switch fi.Mode() & fs.ModeType {
|
|
case 0:
|
|
f, err := fileSys.Open(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
bs, err = io.ReadAll(f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case fs.ModeSymlink:
|
|
t, _ := fileSys.Readlink(path)
|
|
bs = []byte(t)
|
|
}
|
|
files = append(files, FileInfo{
|
|
Path: path,
|
|
Typ: fi.Mode().Type(),
|
|
Executable: fi.Mode()&permMask == permMask && !fi.IsDir(),
|
|
Content: bs,
|
|
})
|
|
return nil
|
|
})
|
|
|
|
return files, err
|
|
}
|
|
|
|
func loadLocalFiles(root string) ([]FileInfo, error) {
|
|
var files []FileInfo
|
|
var err error
|
|
|
|
permMask := fs.FileMode(0111)
|
|
if runtime.GOOS == "windows" {
|
|
permMask = 0
|
|
}
|
|
|
|
err = filepath.Walk(root, func(path string, info fs.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fi, err := os.Lstat(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var bs []byte
|
|
switch fi.Mode() & fs.ModeType {
|
|
case 0:
|
|
bs, err = os.ReadFile(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case fs.ModeSymlink:
|
|
t, _ := os.Readlink(path)
|
|
bs = []byte(filepath.ToSlash(t))
|
|
}
|
|
path, err = filepath.Rel(root, path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
files = append(files, FileInfo{
|
|
Path: filepath.ToSlash(path),
|
|
Typ: fi.Mode().Type(),
|
|
Executable: fi.Mode()&permMask == permMask && !fi.IsDir(),
|
|
Content: bs,
|
|
})
|
|
return nil
|
|
})
|
|
|
|
return files, err
|
|
}
|
|
|
|
func initOSFS(t *testing.T) filesystem.Filesystem {
|
|
wd, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return filesystem.NewOsFilesystem(filepath.Join(wd, templatesPath))
|
|
}
|
|
|
|
func initGitFS(t *testing.T) filesystem.Filesystem {
|
|
repoDir := t.TempDir()
|
|
|
|
err := filepath.Walk(templatesPath, func(path string, fi fs.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
rel, err := filepath.Rel(templatesPath, path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if rel == "" || rel == "." {
|
|
return nil
|
|
}
|
|
|
|
switch {
|
|
case fi.IsDir():
|
|
err = os.Mkdir(filepath.Join(repoDir, rel), fi.Mode().Perm())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case fi.Mode()&fs.ModeSymlink != 0:
|
|
symlinkTarget, err := os.Readlink(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err = os.Symlink(symlinkTarget, filepath.Join(repoDir, rel))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
case fi.Mode()&fs.ModeType == 0: // regular file
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = os.WriteFile(filepath.Join(repoDir, rel), data, fi.Mode().Perm())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
return fmt.Errorf("unsupported file type: %s", fi.Mode().String())
|
|
}
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
r, err := git.PlainInit(repoDir, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
w, err := r.Worktree()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err = w.AddGlob(".")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
author := &object.Signature{Name: "johndoe"}
|
|
_, err = w.Commit("init", &git.CommitOptions{
|
|
Author: author,
|
|
Committer: author,
|
|
All: true,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
uri := fmt.Sprintf(`file://%s`, filepath.ToSlash(repoDir))
|
|
|
|
result, err := fn.FilesystemFromRepo(uri)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func TestCopy(t *testing.T) {
|
|
var err error
|
|
|
|
expectedFiles, err := loadLocalFiles(filepath.Join("testdata", "root"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
zr, err := zip.OpenReader(filepath.Join("testdata", "fs.zip"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { _ = zr.Close() })
|
|
|
|
clone, err := git.Clone(
|
|
memory.NewStorage(),
|
|
memfs.New(),
|
|
&git.CloneOptions{URL: filepath.Join("testdata", "repo.git")},
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
wt, err := clone.Worktree()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
fileSystem filesystem.Filesystem
|
|
}{
|
|
{
|
|
name: "os",
|
|
fileSystem: filesystem.NewOsFilesystem(filepath.Join("testdata", "root")),
|
|
},
|
|
{
|
|
name: "zip",
|
|
fileSystem: filesystem.NewZipFS(&zr.Reader),
|
|
},
|
|
{
|
|
name: "git",
|
|
fileSystem: filesystem.NewBillyFilesystem(wt.Filesystem),
|
|
},
|
|
{
|
|
name: "sub",
|
|
fileSystem: filesystem.NewSubFS("a", mockFS{
|
|
files: []FileInfo{
|
|
{
|
|
Path: "a",
|
|
Typ: fs.ModeDir,
|
|
},
|
|
{
|
|
Path: "a/a",
|
|
Typ: fs.ModeDir,
|
|
},
|
|
{
|
|
Path: "a/a/hello.lnk",
|
|
Typ: fs.ModeSymlink,
|
|
Content: []byte("hello.txt"),
|
|
},
|
|
{
|
|
Path: "a/a/hello.txt",
|
|
Content: []byte("Hello World!\n"),
|
|
},
|
|
},
|
|
}),
|
|
},
|
|
{
|
|
name: "masking",
|
|
fileSystem: filesystem.NewMaskingFS(func(p string) bool {
|
|
return p == "ignored"
|
|
},
|
|
mockFS{
|
|
files: []FileInfo{
|
|
{
|
|
Path: "a",
|
|
Typ: fs.ModeDir,
|
|
},
|
|
{
|
|
Path: "a/hello.lnk",
|
|
Typ: fs.ModeSymlink,
|
|
Content: []byte("hello.txt"),
|
|
},
|
|
{
|
|
Path: "a/hello.txt",
|
|
Content: []byte("Hello World!\n"),
|
|
},
|
|
{
|
|
Path: "ignored",
|
|
Content: []byte("ignored"),
|
|
},
|
|
},
|
|
}),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
dest := t.TempDir()
|
|
err = filesystem.CopyFromFS(".", dest, tt.fileSystem)
|
|
if err != nil {
|
|
t.Errorf("cannot copy: %v", err)
|
|
}
|
|
var actualFiles []FileInfo
|
|
actualFiles, err = loadLocalFiles(dest)
|
|
if err != nil {
|
|
t.Errorf("cannot load local files: %v", err)
|
|
}
|
|
|
|
if diff := cmp.Diff(expectedFiles, actualFiles); diff != "" {
|
|
t.Error("filesystem content missmatch (-want, +got):", diff)
|
|
}
|
|
t.Log(actualFiles)
|
|
})
|
|
}
|
|
}
|
|
|
|
// mock for testing symlink functionality
|
|
type mockFS struct {
|
|
files []FileInfo
|
|
}
|
|
|
|
func (m mockFS) lookupFile(name string) (FileInfo, bool) {
|
|
if name == "." {
|
|
return FileInfo{Path: ".", Typ: fs.ModeDir}, true
|
|
}
|
|
for _, file := range m.files {
|
|
if file.Path == name {
|
|
return file, true
|
|
}
|
|
}
|
|
return FileInfo{}, false
|
|
}
|
|
|
|
type mockFile struct {
|
|
FileInfo
|
|
io.ReadCloser
|
|
}
|
|
|
|
func (m mockFile) Stat() (fs.FileInfo, error) {
|
|
return m.FileInfo, nil
|
|
}
|
|
|
|
func (m mockFS) Open(name string) (fs.File, error) {
|
|
|
|
file, ok := m.lookupFile(name)
|
|
if !ok {
|
|
return nil, fs.ErrNotExist
|
|
}
|
|
return mockFile{FileInfo: file, ReadCloser: io.NopCloser(bytes.NewReader(file.Content))}, nil
|
|
}
|
|
|
|
func (m mockFS) ReadDir(name string) ([]fs.DirEntry, error) {
|
|
_, ok := m.lookupFile(name)
|
|
if !ok {
|
|
return nil, fs.ErrNotExist
|
|
}
|
|
|
|
var dirEntries []fs.DirEntry
|
|
for _, file := range m.files {
|
|
cleanName := strings.TrimRight(file.Path, "/")
|
|
if path.Dir(cleanName) == name {
|
|
dirEntries = append(dirEntries, file)
|
|
}
|
|
}
|
|
return dirEntries, nil
|
|
}
|
|
|
|
func (m mockFS) Stat(name string) (fs.FileInfo, error) {
|
|
file, ok := m.lookupFile(name)
|
|
if !ok {
|
|
return nil, fs.ErrNotExist
|
|
}
|
|
return file, nil
|
|
}
|
|
|
|
func (m mockFS) Readlink(link string) (string, error) {
|
|
file, ok := m.lookupFile(link)
|
|
if !ok {
|
|
return "", fs.ErrNotExist
|
|
}
|
|
if file.Typ != fs.ModeSymlink {
|
|
return "", fs.ErrInvalid
|
|
}
|
|
return string(file.Content), nil
|
|
}
|
|
|
|
func (f FileInfo) Name() string {
|
|
return path.Base(f.Path)
|
|
}
|
|
|
|
func (f FileInfo) Size() int64 {
|
|
return int64(len(f.Content))
|
|
}
|
|
|
|
func (f FileInfo) Mode() fs.FileMode {
|
|
if f.Typ == fs.ModeSymlink {
|
|
return f.Typ | 0777
|
|
}
|
|
if f.Executable || f.Typ == fs.ModeDir {
|
|
return f.Typ | 0755
|
|
}
|
|
return f.Typ | 0644
|
|
}
|
|
|
|
func (f FileInfo) ModTime() time.Time {
|
|
return time.Time{}
|
|
}
|
|
|
|
func (f FileInfo) IsDir() bool {
|
|
return f.Typ.IsDir()
|
|
}
|
|
|
|
func (f FileInfo) Sys() any {
|
|
return nil
|
|
}
|
|
|
|
func (f FileInfo) Type() fs.FileMode {
|
|
return f.Typ
|
|
}
|
|
|
|
func (f FileInfo) Info() (fs.FileInfo, error) {
|
|
return f, nil
|
|
}
|