staros/files_test.go
starainrt d93a851d1b
feat: 完善 staros 系统能力并更新 wincmd 发布版依赖
- 重构 sysconf 为文档模型 INI Parser 与 Config Framework
- 强化 hosts 解析、插入校验、写回与异常输入处理
- 完善 StarCmd 生命周期、等待 API、流式输出与 IO 重定向
- 扩展跨平台文件时间、文件锁、内存、进程与网络能力
- 将 Windows 进程适配更新到 b612.me/wincmd v0.1.0
- 移除本地 wincmd/win32api replace,改用发布版依赖
- 将最低 Go 版本提升到 1.18
- 补充 hosts、sysconf、FileLock、StarCmd 与平台适配回归测试
2026-06-09 18:10:19 +08:00

140 lines
3.7 KiB
Go

package staros
import (
"errors"
"os"
"path/filepath"
"runtime"
"testing"
"time"
)
func Test_FileLock(t *testing.T) {
filename := filepath.Join(t.TempDir(), "test.file")
lock := NewFileLock(filename)
lock2 := NewFileLock(filename)
if err := lock.LockNoBlocking(false); err != nil {
t.Fatal(err)
}
if err := lock2.LockNoBlocking(false); err != nil {
t.Fatal(err)
}
if err := lock.Unlock(); err != nil {
t.Fatal(err)
}
if err := lock2.Unlock(); err != nil {
t.Fatal(err)
}
if err := lock2.LockNoBlocking(true); err != nil {
t.Fatal(err)
}
if err := lock2.Unlock(); err != nil {
t.Fatal(err)
}
_ = os.Remove(filename)
}
func TestFileLockExclusiveConflictTimeout(t *testing.T) {
filename := filepath.Join(t.TempDir(), "timeout.file")
lock := NewFileLock(filename)
contender := NewFileLock(filename)
if err := lock.Lock(true); err != nil {
t.Fatal(err)
}
defer lock.Unlock()
if err := contender.LockNoBlocking(true); !errors.Is(err, ERR_ALREADY_LOCKED) {
if err == nil {
_ = contender.Unlock()
}
t.Fatalf("expected non-blocking exclusive lock conflict, got %v", err)
}
start := time.Now()
if err := contender.LockWithTimeout(50*time.Millisecond, true); !errors.Is(err, ERR_TIMEOUT) {
if err == nil {
_ = contender.Unlock()
}
t.Fatalf("expected exclusive lock timeout, got %v", err)
}
if elapsed := time.Since(start); elapsed > time.Second {
t.Fatalf("lock timeout took too long: %s", elapsed)
}
if err := lock.Unlock(); err != nil {
t.Fatal(err)
}
if err := contender.LockWithTimeout(time.Second, true); err != nil {
t.Fatalf("expected lock after owner unlock, got %v", err)
}
if err := contender.Unlock(); err != nil {
t.Fatal(err)
}
}
func TestFileLockUnlockWithoutSuccessfulLock(t *testing.T) {
filename := filepath.Join(t.TempDir(), "unlock-state.file")
lock := NewFileLock(filename)
if err := lock.Unlock(); !errors.Is(err, errFileLockNotLocked) {
t.Fatalf("expected unlock without lock error, got %v", err)
}
owner := NewFileLock(filename)
contender := NewFileLock(filename)
if err := owner.Lock(true); err != nil {
t.Fatal(err)
}
defer owner.Unlock()
if err := contender.LockNoBlocking(true); !errors.Is(err, ERR_ALREADY_LOCKED) {
if err == nil {
_ = contender.Unlock()
}
t.Fatalf("expected lock conflict, got %v", err)
}
if err := contender.Unlock(); !errors.Is(err, errFileLockNotLocked) {
t.Fatalf("failed lock attempt should not be unlockable, got %v", err)
}
if err := owner.Unlock(); err != nil {
t.Fatal(err)
}
}
func TestFileLockRejectsSecondLockOnSameObject(t *testing.T) {
filename := filepath.Join(t.TempDir(), "same-object.file")
lock := NewFileLock(filename)
if err := lock.Lock(true); err != nil {
t.Fatal(err)
}
defer lock.Unlock()
for name, fn := range map[string]func() error{
"Lock": func() error { return lock.Lock(true) },
"LockNoBlocking": func() error { return lock.LockNoBlocking(true) },
"LockWithTimeout": func() error { return lock.LockWithTimeout(time.Second, true) },
} {
if err := fn(); !errors.Is(err, ERR_ALREADY_LOCKED) {
if err == nil {
_ = lock.Unlock()
}
t.Fatalf("expected %s on same lock object to reject second lock, got %v", name, err)
}
}
}
func TestGetFileCreationTimeLinuxUnavailable(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skip("linux-only creation time fallback")
}
filename := filepath.Join(t.TempDir(), "creation-time.file")
if err := os.WriteFile(filename, []byte("demo"), 0o644); err != nil {
t.Fatal(err)
}
info, err := os.Stat(filename)
if err != nil {
t.Fatal(err)
}
if got := GetFileCreationTime(info); !got.IsZero() {
t.Fatalf("linux FileInfo should not report synthetic creation time, got %s", got)
}
}