mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
0276554d9c
Add comprehensive test coverage for two previously untested packages: pkg/fileutil (9 tests): - Basic write and read-back - File permissions (0600) - Overwrite existing files - Empty data handling - Nested directory auto-creation - No temp files left after success - Large file (1MB) handling - Concurrent write safety - Invalid path error handling pkg/health (15 tests): - Health endpoint returns 200 with status, uptime, pid - Ready endpoint returns 503 when not ready - Ready endpoint returns 200 when ready - Ready fails when any registered check fails - Ready passes with all checks passing - Reload rejects non-POST methods - Reload returns 503 when no reload func set - Reload calls registered function on success - Reload returns 500 on function error - SetReady toggle behavior - Multiple health checks interaction - RegisterOnMux works with custom ServeMux - NewServer defaults - StartContext graceful shutdown on cancel - statusString helper
177 lines
4.0 KiB
Go
177 lines
4.0 KiB
Go
package fileutil
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
func TestWriteFileAtomic_Basic(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "test.txt")
|
|
data := []byte("hello picoclaw")
|
|
|
|
err := WriteFileAtomic(path, data, 0o644)
|
|
if err != nil {
|
|
t.Fatalf("WriteFileAtomic failed: %v", err)
|
|
}
|
|
|
|
got, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("ReadFile failed: %v", err)
|
|
}
|
|
if string(got) != string(data) {
|
|
t.Errorf("got %q, want %q", got, data)
|
|
}
|
|
}
|
|
|
|
func TestWriteFileAtomic_Permissions(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "secret.txt")
|
|
|
|
err := WriteFileAtomic(path, []byte("secret"), 0o600)
|
|
if err != nil {
|
|
t.Fatalf("WriteFileAtomic failed: %v", err)
|
|
}
|
|
|
|
info, err := os.Stat(path)
|
|
if err != nil {
|
|
t.Fatalf("Stat failed: %v", err)
|
|
}
|
|
// On Unix, check file mode (ignoring directory bits)
|
|
if got := info.Mode().Perm(); got != 0o600 {
|
|
t.Errorf("permissions = %o, want %o", got, 0o600)
|
|
}
|
|
}
|
|
|
|
func TestWriteFileAtomic_Overwrite(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "overwrite.txt")
|
|
|
|
// Write initial content
|
|
if err := WriteFileAtomic(path, []byte("old"), 0o644); err != nil {
|
|
t.Fatalf("first write failed: %v", err)
|
|
}
|
|
|
|
// Overwrite
|
|
if err := WriteFileAtomic(path, []byte("new"), 0o644); err != nil {
|
|
t.Fatalf("second write failed: %v", err)
|
|
}
|
|
|
|
got, _ := os.ReadFile(path)
|
|
if string(got) != "new" {
|
|
t.Errorf("got %q after overwrite, want %q", got, "new")
|
|
}
|
|
}
|
|
|
|
func TestWriteFileAtomic_EmptyData(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "empty.txt")
|
|
|
|
err := WriteFileAtomic(path, []byte{}, 0o644)
|
|
if err != nil {
|
|
t.Fatalf("WriteFileAtomic with empty data failed: %v", err)
|
|
}
|
|
|
|
got, _ := os.ReadFile(path)
|
|
if len(got) != 0 {
|
|
t.Errorf("expected empty file, got %d bytes", len(got))
|
|
}
|
|
}
|
|
|
|
func TestWriteFileAtomic_CreatesParentDirs(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "a", "b", "c", "deep.txt")
|
|
|
|
err := WriteFileAtomic(path, []byte("deep"), 0o644)
|
|
if err != nil {
|
|
t.Fatalf("WriteFileAtomic with nested dirs failed: %v", err)
|
|
}
|
|
|
|
got, _ := os.ReadFile(path)
|
|
if string(got) != "deep" {
|
|
t.Errorf("got %q, want %q", got, "deep")
|
|
}
|
|
}
|
|
|
|
func TestWriteFileAtomic_NoTempFileOnSuccess(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "clean.txt")
|
|
|
|
if err := WriteFileAtomic(path, []byte("data"), 0o644); err != nil {
|
|
t.Fatalf("WriteFileAtomic failed: %v", err)
|
|
}
|
|
|
|
// Verify no temp files remain
|
|
entries, _ := os.ReadDir(dir)
|
|
for _, e := range entries {
|
|
if e.Name() != "clean.txt" {
|
|
t.Errorf("unexpected file remaining: %s", e.Name())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestWriteFileAtomic_LargeFile(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "large.bin")
|
|
|
|
// 1MB of data
|
|
data := make([]byte, 1<<20)
|
|
for i := range data {
|
|
data[i] = byte(i % 256)
|
|
}
|
|
|
|
if err := WriteFileAtomic(path, data, 0o644); err != nil {
|
|
t.Fatalf("WriteFileAtomic with large file failed: %v", err)
|
|
}
|
|
|
|
got, _ := os.ReadFile(path)
|
|
if len(got) != len(data) {
|
|
t.Errorf("file size = %d, want %d", len(got), len(data))
|
|
}
|
|
}
|
|
|
|
func TestWriteFileAtomic_Concurrent(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "concurrent.txt")
|
|
|
|
var wg sync.WaitGroup
|
|
errs := make(chan error, 10)
|
|
|
|
for i := 0; i < 10; i++ {
|
|
wg.Add(1)
|
|
go func(n int) {
|
|
defer wg.Done()
|
|
data := []byte(string(rune('A' + n)))
|
|
if err := WriteFileAtomic(path, data, 0o644); err != nil {
|
|
errs <- err
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
close(errs)
|
|
|
|
for err := range errs {
|
|
t.Errorf("concurrent write error: %v", err)
|
|
}
|
|
|
|
// File should exist and contain exactly 1 byte (last writer wins)
|
|
got, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("ReadFile after concurrent writes failed: %v", err)
|
|
}
|
|
if len(got) != 1 {
|
|
t.Errorf("expected 1 byte after concurrent writes, got %d", len(got))
|
|
}
|
|
}
|
|
|
|
func TestWriteFileAtomic_InvalidPath(t *testing.T) {
|
|
// /dev/null/impossible is not a valid path on any OS
|
|
err := WriteFileAtomic("/dev/null/impossible/file.txt", []byte("data"), 0o644)
|
|
if err == nil {
|
|
t.Error("expected error for invalid path, got nil")
|
|
}
|
|
}
|