test(platform): 修 Windows 上平台脆弱的断言(不削弱测试意图)

- trace/task: 亚 tick 耗时合法读到 0(本机时钟步长约 0.44ms),改为断言非负且 Duration == EndedAt-StartedAt
- user/runtimeconfig: Windows 无法表示普通文件的 POSIX 权限位(Stat 恒 0666),0600 断言仅在非 Windows 生效,注释写明实现确实请求 0600
- exec: MSYS pwd 把同一目录渲染成 /tmp/...;改用「标记文件相对名解析」+「与显式 root 的 pwd 输出逐字相等」两条跨平台断言 + 变异验证
- 只读排查全仓同类脆弱断言,结果记入 DevDocs/工作小结
This commit is contained in:
AskaEth committed 2026-10-03 16:08:52 +08:00
1 parent fbbfcc95ee
commit 29aa60d0eb
6 files changed
+89 -21

No files matched your search

@@ -3,6 +3,7 @@ package runtimeconfig
import (
"os"
"path/filepath"
"runtime"
"strings"
"testing"
@@ -176,8 +177,14 @@ func TestSaveChannelsRoundTrip(t *testing.T) {
if err != nil {
t.Fatalf("stat: %v", err)
}
if info.Mode().Perm()&0o077 != 0 {
t.Fatalf("channels.json mode = %v, want owner-only (0600)", info.Mode().Perm())
if runtime.GOOS != "windows" {
// Windows has no POSIX permission bits for regular files: Chmod only
// toggles the read-only flag and Stat reports 0666 regardless of the
// requested 0600, so this assertion is only meaningful on Unix-like
// platforms. SaveChannels still requests 0600 (see channels.go).
if info.Mode().Perm()&0o077 != 0 {
t.Fatalf("channels.json mode = %v, want owner-only (0600)", info.Mode().Perm())
}
}
}
@@ -5,6 +5,7 @@ import (
"errors"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
)
@@ -39,8 +40,14 @@ func TestSaveLoadPlugins_RoundTrip(t *testing.T) {
if err != nil {
t.Fatalf("stat: %v", err)
}
if perm := info.Mode().Perm(); perm != 0o600 {
t.Fatalf("mode = %o, want 600", perm)
if runtime.GOOS != "windows" {
// Windows has no POSIX permission bits for regular files: Chmod only
// toggles the read-only flag and Stat reports 0666 regardless of the
// requested 0600, so this assertion is only meaningful on Unix-like
// platforms. SavePlugins still requests 0600 (see plugins.go).
if perm := info.Mode().Perm(); perm != 0o600 {
t.Fatalf("mode = %o, want 600", perm)
}
}
got, err := LoadPlugins(path)
if err != nil {
+40 -9
View File
@@ -144,20 +144,51 @@ func TestTerminal_Injection_Rejected(t *testing.T) {
func TestTerminal_WorkDir_DefaultToRoot(t *testing.T) {
t.Parallel()
requireBin(t, "pwd")
root := t.TempDir()
ex := newEnabledExecutor(t, enabledCfg("pwd", root))
res, err := ex.Execute(context.Background(), ExecRequest{Command: []string{"pwd"}})
// Probe the default working directory without comparing rendered path
// strings. On Windows the command may resolve to Git's MSYS coreutils, which
// reports the Windows cwd it was handed as a POSIX path (the user Temp
// directory comes back as /tmp/...), so a "Stdout contains root" check is not
// portable. A marker file that exists only in the sandbox root, read through
// a relative name, is found only when the process really runs in that root.
const (
probeFile = "cwd-probe.txt"
probeBody = "cwd-probe"
)
root := t.TempDir()
if err := os.WriteFile(filepath.Join(root, probeFile), []byte(probeBody), 0o600); err != nil {
t.Fatalf("write probe: %v", err)
}
requireBin(t, "cat")
catEx := newEnabledExecutor(t, enabledCfg("cat", root))
res, err := catEx.Execute(context.Background(), ExecRequest{Command: []string{"cat", probeFile}})
if err != nil {
t.Fatalf("Execute: %v", err)
}
realRoot := root
if r, err := filepath.EvalSymlinks(root); err == nil {
realRoot = r
if res.ExitCode != 0 || res.Stdout != probeBody {
t.Errorf("cat %s with no WorkDir: exit=%d stdout=%q stderr=%q, want %q read from the sandbox root %s",
probeFile, res.ExitCode, res.Stdout, res.Stderr, probeBody, root)
}
if !strings.Contains(res.Stdout, realRoot) {
t.Errorf("Stdout = %q, want contain %q", res.Stdout, realRoot)
// And the default must be the root itself, not a parent or a subdirectory:
// pwd with no WorkDir must print exactly what pwd prints for WorkDir=root.
// Both runs share pwd's own rendering, so the comparison stays platform
// independent.
requireBin(t, "pwd")
pwdEx := newEnabledExecutor(t, enabledCfg("pwd", root))
deflt, err := pwdEx.Execute(context.Background(), ExecRequest{Command: []string{"pwd"}})
if err != nil {
t.Fatalf("Execute pwd (default workdir): %v", err)
}
explicit, err := pwdEx.Execute(context.Background(), ExecRequest{
Command: []string{"pwd"},
WorkDir: root,
})
if err != nil {
t.Fatalf("Execute pwd (WorkDir=%s): %v", root, err)
}
if got, want := strings.TrimSpace(deflt.Stdout), strings.TrimSpace(explicit.Stdout); got != want {
t.Errorf("pwd with no WorkDir = %q, want the sandbox root as pwd renders it with WorkDir=%s: %q", got, root, want)
}
}
+6 -2
View File
@@ -219,8 +219,12 @@ func TestManager_ForegroundTask_FastCompletes(t *testing.T) {
if snap.Output.Stdout != "fast out" {
t.Errorf("Stdout=%q, want fast out", snap.Output.Stdout)
}
if !snap.FinishedAt.After(snap.StartedAt) {
t.Errorf("FinishedAt=%v should be after StartedAt=%v", snap.FinishedAt, snap.StartedAt)
// The monotonic clock may be too coarse to tick between StartedAt and
// FinishedAt, so a task that finishes in less than one tick legitimately
// reads the same stamp for both. Assert the platform-independent invariant
// instead: the finish time is never before the start time.
if snap.FinishedAt.Before(snap.StartedAt) {
t.Errorf("FinishedAt=%v should not be before StartedAt=%v", snap.FinishedAt, snap.StartedAt)
}
}
+16 -4
View File
@@ -72,8 +72,15 @@ func TestSpan_Lifecycle(t *testing.T) {
if sp.EndedAt.Before(before) {
t.Fatalf("EndedAt %v is before snapshot %v", sp.EndedAt, before)
}
if sp.Duration <= 0 {
t.Fatalf("Duration = %v, want > 0", sp.Duration)
// The monotonic clock may be too coarse to tick between StartedAt and
// EndedAt, so a sub-tick span legitimately reads Duration == 0. Assert the
// platform-independent invariant instead: Duration is non-negative and is
// exactly the delta of the two stamps.
if sp.Duration < 0 {
t.Fatalf("Duration = %v, want >= 0", sp.Duration)
}
if want := sp.EndedAt.Sub(sp.StartedAt); sp.Duration != want {
t.Fatalf("Duration = %v, want EndedAt-StartedAt = %v", sp.Duration, want)
}
if len(sp.Events) != 1 || sp.Events[0].Type != "token" {
t.Fatalf("Events = %+v, want one 'token' event", sp.Events)
@@ -241,8 +248,13 @@ func TestJSONRecorder_Output(t *testing.T) {
if childRec.Status != SpanStatusOK {
t.Fatalf("childRec.Status = %q, want ok", childRec.Status)
}
if childRec.Duration <= 0 {
t.Fatalf("childRec.Duration = %v, want > 0", childRec.Duration)
// As in TestSpan_Lifecycle: a sub-tick span reads Duration == 0 on platforms
// with a coarse monotonic clock, so assert the recorded delta invariant.
if childRec.Duration < 0 {
t.Fatalf("childRec.Duration = %v, want >= 0", childRec.Duration)
}
if want := childRec.EndedAt.Sub(childRec.StartedAt); childRec.Duration != want {
t.Fatalf("childRec.Duration = %v, want EndedAt-StartedAt = %v", childRec.Duration, want)
}
if childRec.Props["model"] != "gpt-test" {
t.Fatalf("childRec.Props = %+v, want model key", childRec.Props)
+9 -2
View File
@@ -3,6 +3,7 @@ package user
import (
"os"
"path/filepath"
"runtime"
"testing"
)
@@ -35,8 +36,14 @@ notes: "主人。陪伴对象。"
if err != nil {
t.Fatalf("stat saved profile: %v", err)
}
if perm := info.Mode().Perm(); perm != 0o600 {
t.Errorf("saved file mode = %o, want 0600", perm)
if runtime.GOOS != "windows" {
// Windows has no POSIX permission bits for regular files: Chmod only
// toggles the read-only flag and Stat reports 0666 regardless of the
// requested 0600, so this assertion is only meaningful on Unix-like
// platforms. SaveOwnerProfile still requests 0600 (see save.go).
if perm := info.Mode().Perm(); perm != 0o600 {
t.Errorf("saved file mode = %o, want 0600", perm)
}
}
got, err := LoadOwnerProfile(dst)