blob: 11a238341909249fbe086a96fe984fa72ad52952 [file] [edit]
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fileutil
import (
"bytes"
"os"
"path/filepath"
"testing"
"testing/fstest"
)
func TestCopyFile(t *testing.T) {
tempDir := t.TempDir()
srcPath := filepath.Join(tempDir, "src.txt")
srcContent := []byte("hello world 1234567890")
if err := os.WriteFile(srcPath, srcContent, 0644); err != nil {
t.Fatal(err)
}
t.Run("NewFile", func(t *testing.T) {
dstPath := filepath.Join(tempDir, "dst_new.txt")
if err := CopyFile(dstPath, srcPath, nil, nil); err != nil {
t.Fatalf("CopyFile failed: %v", err)
}
got, err := os.ReadFile(dstPath)
if err != nil {
t.Fatalf("ReadFile failed: %v", err)
}
if !bytes.Equal(got, srcContent) {
t.Fatalf("got %q, want %q", got, srcContent)
}
})
t.Run("DstExistsSameContent", func(t *testing.T) {
dstPath := filepath.Join(tempDir, "dst_same.txt")
if err := os.WriteFile(dstPath, srcContent, 0644); err != nil {
t.Fatal(err)
}
fi, err := os.Stat(dstPath)
if err != nil {
t.Fatal(err)
}
modTime := fi.ModTime()
if err := CopyFile(dstPath, srcPath, nil, nil); err != nil {
t.Fatalf("CopyFile failed: %v", err)
}
got, err := os.ReadFile(dstPath)
if err != nil {
t.Fatalf("ReadFile failed: %v", err)
}
if !bytes.Equal(got, srcContent) {
t.Fatalf("got %q, want %q", got, srcContent)
}
fi2, err := os.Stat(dstPath)
if err != nil {
t.Fatal(err)
}
if !fi2.ModTime().Equal(modTime) {
t.Errorf("file was modified/rewritten, modTime changed from %v to %v", modTime, fi2.ModTime())
}
})
t.Run("DstExistsDifferentContentSameSize", func(t *testing.T) {
dstPath := filepath.Join(tempDir, "dst_diff_same_size.txt")
diffContent := []byte("hello world 0987654321") // same length as srcContent
if err := os.WriteFile(dstPath, diffContent, 0644); err != nil {
t.Fatal(err)
}
if err := CopyFile(dstPath, srcPath, nil, nil); err != nil {
t.Fatalf("CopyFile failed: %v", err)
}
got, err := os.ReadFile(dstPath)
if err != nil {
t.Fatalf("ReadFile failed: %v", err)
}
if !bytes.Equal(got, srcContent) {
t.Fatalf("got %q, want %q", got, srcContent)
}
})
t.Run("DstExistsDifferentSize", func(t *testing.T) {
dstPath := filepath.Join(tempDir, "dst_diff_size.txt")
diffContent := []byte("short content")
if err := os.WriteFile(dstPath, diffContent, 0644); err != nil {
t.Fatal(err)
}
if err := CopyFile(dstPath, srcPath, nil, nil); err != nil {
t.Fatalf("CopyFile failed: %v", err)
}
got, err := os.ReadFile(dstPath)
if err != nil {
t.Fatalf("ReadFile failed: %v", err)
}
if !bytes.Equal(got, srcContent) {
t.Fatalf("got %q, want %q", got, srcContent)
}
})
t.Run("SameSrcAndDst", func(t *testing.T) {
filePath := filepath.Join(tempDir, "same.txt")
if err := os.WriteFile(filePath, srcContent, 0644); err != nil {
t.Fatal(err)
}
if err := CopyFile(filePath, filePath, nil, nil); err != nil {
t.Fatalf("CopyFile failed: %v", err)
}
got, err := os.ReadFile(filePath)
if err != nil {
t.Fatalf("ReadFile failed: %v", err)
}
if !bytes.Equal(got, srcContent) {
t.Fatalf("file content corrupted; got %q, want %q", got, srcContent)
}
})
t.Run("SrcFS", func(t *testing.T) {
mapFS := fstest.MapFS{
"virtual.txt": &fstest.MapFile{
Data: srcContent,
Mode: 0644,
},
}
dstPath := filepath.Join(tempDir, "dst_virtual.txt")
if err := CopyFile(dstPath, "virtual.txt", nil, mapFS); err != nil {
t.Fatalf("CopyFile failed: %v", err)
}
got, err := os.ReadFile(dstPath)
if err != nil {
t.Fatalf("ReadFile failed: %v", err)
}
if !bytes.Equal(got, srcContent) {
t.Fatalf("got %q, want %q", got, srcContent)
}
// Copy again when dst exists and has same content
if err := CopyFile(dstPath, "virtual.txt", nil, mapFS); err != nil {
t.Fatalf("CopyFile second time failed: %v", err)
}
})
t.Run("LargeFiles", func(t *testing.T) {
largeSrc := filepath.Join(tempDir, "large_src.dat")
largeDst := filepath.Join(tempDir, "large_dst.dat")
// Create 150KB data (> 64KB chunk buffer)
data := make([]byte, 150*1024)
for i := range data {
data[i] = byte(i % 251)
}
if err := os.WriteFile(largeSrc, data, 0644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(largeDst, data, 0644); err != nil {
t.Fatal(err)
}
fi, err := os.Stat(largeDst)
if err != nil {
t.Fatal(err)
}
modTime := fi.ModTime()
if err := CopyFile(largeDst, largeSrc, nil, nil); err != nil {
t.Fatalf("CopyFile failed: %v", err)
}
fi2, err := os.Stat(largeDst)
if err != nil {
t.Fatal(err)
}
if !fi2.ModTime().Equal(modTime) {
t.Errorf("large file was unnecessarily rewritten")
}
// Mutate largeDst at offset 100,000
dataDiff := make([]byte, len(data))
copy(dataDiff, data)
dataDiff[100000] ^= 0xFF
if err := os.WriteFile(largeDst, dataDiff, 0644); err != nil {
t.Fatal(err)
}
if err := CopyFile(largeDst, largeSrc, nil, nil); err != nil {
t.Fatalf("CopyFile failed: %v", err)
}
got, err := os.ReadFile(largeDst)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(got, data) {
t.Fatalf("CopyFile failed to overwrite modified large file")
}
})
}