315 lines
6.5 KiB
Go
315 lines
6.5 KiB
Go
|
|
package tarfs
|
||
|
|
|
||
|
|
import (
|
||
|
|
"archive/tar"
|
||
|
|
"bytes"
|
||
|
|
"io"
|
||
|
|
"os"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
// createTestTar creates a tar archive with the given files in rootfs/ prefix
|
||
|
|
func createTestTar(files map[string][]byte) ([]byte, error) {
|
||
|
|
buf := new(bytes.Buffer)
|
||
|
|
tw := tar.NewWriter(buf)
|
||
|
|
|
||
|
|
for name, content := range files {
|
||
|
|
hdr := &tar.Header{
|
||
|
|
Name: "rootfs/" + name,
|
||
|
|
Mode: 0644,
|
||
|
|
Size: int64(len(content)),
|
||
|
|
ModTime: time.Now(),
|
||
|
|
}
|
||
|
|
if err := tw.WriteHeader(hdr); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
if _, err := tw.Write(content); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if err := tw.Close(); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
return buf.Bytes(), nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestNew(t *testing.T) {
|
||
|
|
t.Run("valid tar", func(t *testing.T) {
|
||
|
|
files := map[string][]byte{
|
||
|
|
"test.txt": []byte("Hello, World!"),
|
||
|
|
"subdir/file.txt": []byte("Nested file"),
|
||
|
|
}
|
||
|
|
tarData, err := createTestTar(files)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Failed to create test tar: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
fs, err := New(tarData)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("New() error = %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if fs == nil {
|
||
|
|
t.Fatal("New() returned nil")
|
||
|
|
}
|
||
|
|
|
||
|
|
if len(fs.files) != 2 {
|
||
|
|
t.Errorf("Expected 2 files, got %d", len(fs.files))
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
t.Run("empty tar", func(t *testing.T) {
|
||
|
|
buf := new(bytes.Buffer)
|
||
|
|
tw := tar.NewWriter(buf)
|
||
|
|
tw.Close()
|
||
|
|
|
||
|
|
fs, err := New(buf.Bytes())
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("New() error = %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if len(fs.files) != 0 {
|
||
|
|
t.Errorf("Expected 0 files, got %d", len(fs.files))
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
t.Run("invalid tar", func(t *testing.T) {
|
||
|
|
_, err := New([]byte("not a tar file"))
|
||
|
|
if err == nil {
|
||
|
|
t.Error("Expected error for invalid tar data")
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
t.Run("files without rootfs prefix are ignored", func(t *testing.T) {
|
||
|
|
buf := new(bytes.Buffer)
|
||
|
|
tw := tar.NewWriter(buf)
|
||
|
|
|
||
|
|
// Add file without rootfs/ prefix
|
||
|
|
hdr := &tar.Header{
|
||
|
|
Name: "outside.txt",
|
||
|
|
Mode: 0644,
|
||
|
|
Size: 5,
|
||
|
|
}
|
||
|
|
tw.WriteHeader(hdr)
|
||
|
|
tw.Write([]byte("hello"))
|
||
|
|
|
||
|
|
// Add file with rootfs/ prefix
|
||
|
|
hdr = &tar.Header{
|
||
|
|
Name: "rootfs/inside.txt",
|
||
|
|
Mode: 0644,
|
||
|
|
Size: 5,
|
||
|
|
}
|
||
|
|
tw.WriteHeader(hdr)
|
||
|
|
tw.Write([]byte("world"))
|
||
|
|
|
||
|
|
tw.Close()
|
||
|
|
|
||
|
|
fs, err := New(buf.Bytes())
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("New() error = %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Only the rootfs file should be included
|
||
|
|
if len(fs.files) != 1 {
|
||
|
|
t.Errorf("Expected 1 file, got %d", len(fs.files))
|
||
|
|
}
|
||
|
|
|
||
|
|
if _, ok := fs.files["inside.txt"]; !ok {
|
||
|
|
t.Error("Expected 'inside.txt' to be in files")
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestTarFS_Open(t *testing.T) {
|
||
|
|
files := map[string][]byte{
|
||
|
|
"test.txt": []byte("Hello, World!"),
|
||
|
|
"subdir/file.txt": []byte("Nested file"),
|
||
|
|
}
|
||
|
|
tarData, err := createTestTar(files)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Failed to create test tar: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
fs, err := New(tarData)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("New() error = %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
t.Run("existing file", func(t *testing.T) {
|
||
|
|
file, err := fs.Open("test.txt")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Open() error = %v", err)
|
||
|
|
}
|
||
|
|
defer file.Close()
|
||
|
|
|
||
|
|
content, err := io.ReadAll(file)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("ReadAll() error = %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if string(content) != "Hello, World!" {
|
||
|
|
t.Errorf("Got %q, want %q", string(content), "Hello, World!")
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
t.Run("existing file with leading slash", func(t *testing.T) {
|
||
|
|
file, err := fs.Open("/test.txt")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Open() error = %v", err)
|
||
|
|
}
|
||
|
|
defer file.Close()
|
||
|
|
|
||
|
|
content, err := io.ReadAll(file)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("ReadAll() error = %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if string(content) != "Hello, World!" {
|
||
|
|
t.Errorf("Got %q, want %q", string(content), "Hello, World!")
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
t.Run("nested file", func(t *testing.T) {
|
||
|
|
file, err := fs.Open("subdir/file.txt")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Open() error = %v", err)
|
||
|
|
}
|
||
|
|
defer file.Close()
|
||
|
|
|
||
|
|
content, err := io.ReadAll(file)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("ReadAll() error = %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if string(content) != "Nested file" {
|
||
|
|
t.Errorf("Got %q, want %q", string(content), "Nested file")
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
t.Run("non-existing file", func(t *testing.T) {
|
||
|
|
_, err := fs.Open("nonexistent.txt")
|
||
|
|
if err != os.ErrNotExist {
|
||
|
|
t.Errorf("Expected os.ErrNotExist, got %v", err)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
t.Run("multiple reads reset position", func(t *testing.T) {
|
||
|
|
// First read
|
||
|
|
file1, _ := fs.Open("test.txt")
|
||
|
|
content1, _ := io.ReadAll(file1)
|
||
|
|
file1.Close()
|
||
|
|
|
||
|
|
// Second read should work too
|
||
|
|
file2, _ := fs.Open("test.txt")
|
||
|
|
content2, _ := io.ReadAll(file2)
|
||
|
|
file2.Close()
|
||
|
|
|
||
|
|
if string(content1) != string(content2) {
|
||
|
|
t.Errorf("Multiple reads returned different content")
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestTarFile_Methods(t *testing.T) {
|
||
|
|
files := map[string][]byte{
|
||
|
|
"test.txt": []byte("Hello, World!"),
|
||
|
|
}
|
||
|
|
tarData, err := createTestTar(files)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Failed to create test tar: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
fs, err := New(tarData)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("New() error = %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
file, err := fs.Open("test.txt")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Open() error = %v", err)
|
||
|
|
}
|
||
|
|
defer file.Close()
|
||
|
|
|
||
|
|
t.Run("Read", func(t *testing.T) {
|
||
|
|
buf := make([]byte, 5)
|
||
|
|
n, err := file.Read(buf)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Read() error = %v", err)
|
||
|
|
}
|
||
|
|
if n != 5 {
|
||
|
|
t.Errorf("Read() returned %d bytes, want 5", n)
|
||
|
|
}
|
||
|
|
if string(buf) != "Hello" {
|
||
|
|
t.Errorf("Got %q, want %q", string(buf), "Hello")
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
t.Run("Seek", func(t *testing.T) {
|
||
|
|
pos, err := file.Seek(0, io.SeekStart)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Seek() error = %v", err)
|
||
|
|
}
|
||
|
|
if pos != 0 {
|
||
|
|
t.Errorf("Seek() returned position %d, want 0", pos)
|
||
|
|
}
|
||
|
|
|
||
|
|
pos, err = file.Seek(7, io.SeekStart)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Seek() error = %v", err)
|
||
|
|
}
|
||
|
|
if pos != 7 {
|
||
|
|
t.Errorf("Seek() returned position %d, want 7", pos)
|
||
|
|
}
|
||
|
|
|
||
|
|
buf := make([]byte, 6)
|
||
|
|
file.Read(buf)
|
||
|
|
if string(buf) != "World!" {
|
||
|
|
t.Errorf("After seek, got %q, want %q", string(buf), "World!")
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
t.Run("Readdir", func(t *testing.T) {
|
||
|
|
_, err := file.Readdir(0)
|
||
|
|
if err != os.ErrInvalid {
|
||
|
|
t.Errorf("Readdir() should return os.ErrInvalid, got %v", err)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
t.Run("Stat", func(t *testing.T) {
|
||
|
|
info, err := file.Stat()
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Stat() error = %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if info.Name() != "test.txt" {
|
||
|
|
t.Errorf("Name() = %q, want %q", info.Name(), "test.txt")
|
||
|
|
}
|
||
|
|
|
||
|
|
if info.Size() != 13 {
|
||
|
|
t.Errorf("Size() = %d, want 13", info.Size())
|
||
|
|
}
|
||
|
|
|
||
|
|
if info.Mode() != 0444 {
|
||
|
|
t.Errorf("Mode() = %v, want 0444", info.Mode())
|
||
|
|
}
|
||
|
|
|
||
|
|
if info.IsDir() {
|
||
|
|
t.Error("IsDir() should be false")
|
||
|
|
}
|
||
|
|
|
||
|
|
if info.Sys() != nil {
|
||
|
|
t.Error("Sys() should return nil")
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
t.Run("Close", func(t *testing.T) {
|
||
|
|
err := file.Close()
|
||
|
|
if err != nil {
|
||
|
|
t.Errorf("Close() error = %v", err)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|