mirror of
https://github.com/getcompanion-ai/computer-host.git
synced 2026-04-15 03:00:42 +00:00
94 lines
2.5 KiB
Go
94 lines
2.5 KiB
Go
package daemon
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"syscall"
|
|
"testing"
|
|
)
|
|
|
|
func TestCloneFilePreservesSparseDiskUsage(t *testing.T) {
|
|
root := t.TempDir()
|
|
sourcePath := filepath.Join(root, "source.img")
|
|
targetPath := filepath.Join(root, "target.img")
|
|
|
|
sourceFile, err := os.OpenFile(sourcePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644)
|
|
if err != nil {
|
|
t.Fatalf("open source file: %v", err)
|
|
}
|
|
if _, err := sourceFile.Write([]byte("head")); err != nil {
|
|
_ = sourceFile.Close()
|
|
t.Fatalf("write source prefix: %v", err)
|
|
}
|
|
if _, err := sourceFile.Seek(32<<20, io.SeekStart); err != nil {
|
|
_ = sourceFile.Close()
|
|
t.Fatalf("seek source hole: %v", err)
|
|
}
|
|
if _, err := sourceFile.Write([]byte("tail")); err != nil {
|
|
_ = sourceFile.Close()
|
|
t.Fatalf("write source suffix: %v", err)
|
|
}
|
|
if err := sourceFile.Close(); err != nil {
|
|
t.Fatalf("close source file: %v", err)
|
|
}
|
|
|
|
sourceInfo, err := os.Stat(sourcePath)
|
|
if err != nil {
|
|
t.Fatalf("stat source file: %v", err)
|
|
}
|
|
sourceUsage, err := allocatedBytes(sourcePath)
|
|
if err != nil {
|
|
t.Fatalf("allocated bytes for source: %v", err)
|
|
}
|
|
if sourceUsage >= sourceInfo.Size()/2 {
|
|
t.Skip("temp filesystem does not expose sparse allocation savings")
|
|
}
|
|
|
|
if err := cloneFile(sourcePath, targetPath); err != nil {
|
|
t.Fatalf("clone sparse file: %v", err)
|
|
}
|
|
|
|
targetInfo, err := os.Stat(targetPath)
|
|
if err != nil {
|
|
t.Fatalf("stat target file: %v", err)
|
|
}
|
|
if targetInfo.Size() != sourceInfo.Size() {
|
|
t.Fatalf("target size mismatch: got %d want %d", targetInfo.Size(), sourceInfo.Size())
|
|
}
|
|
|
|
targetUsage, err := allocatedBytes(targetPath)
|
|
if err != nil {
|
|
t.Fatalf("allocated bytes for target: %v", err)
|
|
}
|
|
if targetUsage >= targetInfo.Size()/2 {
|
|
t.Fatalf("target file is not sparse enough: allocated=%d size=%d", targetUsage, targetInfo.Size())
|
|
}
|
|
|
|
targetData, err := os.ReadFile(targetPath)
|
|
if err != nil {
|
|
t.Fatalf("read target file: %v", err)
|
|
}
|
|
if !bytes.Equal(targetData[:4], []byte("head")) {
|
|
t.Fatalf("target prefix mismatch: %q", string(targetData[:4]))
|
|
}
|
|
if !bytes.Equal(targetData[len(targetData)-4:], []byte("tail")) {
|
|
t.Fatalf("target suffix mismatch: %q", string(targetData[len(targetData)-4:]))
|
|
}
|
|
if !bytes.Equal(targetData[4:4+(1<<20)], make([]byte, 1<<20)) {
|
|
t.Fatal("target hole contents were not zeroed")
|
|
}
|
|
}
|
|
|
|
func allocatedBytes(path string) (int64, error) {
|
|
info, err := os.Stat(path)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
stat, ok := info.Sys().(*syscall.Stat_t)
|
|
if !ok {
|
|
return 0, syscall.EINVAL
|
|
}
|
|
return stat.Blocks * 512, nil
|
|
}
|