diff --git a/.gitignore b/.gitignore index 31995b5..514307f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ __pycache__ .coverage coverage.out references +output/ diff --git a/codeagent-wrapper/internal/app/executor_concurrent_test.go b/codeagent-wrapper/internal/app/executor_concurrent_test.go index db0fc74..f6c7e8b 100644 --- a/codeagent-wrapper/internal/app/executor_concurrent_test.go +++ b/codeagent-wrapper/internal/app/executor_concurrent_test.go @@ -567,8 +567,7 @@ func TestExecutorParallelLogIsolation(t *testing.T) { } func TestConcurrentExecutorParallelLogIsolationAndClosure(t *testing.T) { - tempDir := t.TempDir() - t.Setenv("TMPDIR", tempDir) + setTempDirEnv(t, t.TempDir()) oldArgs := os.Args os.Args = []string{wrapperName} @@ -929,8 +928,7 @@ func TestExecutorExecuteConcurrentWithContextBranches(t *testing.T) { t.Run("TestConcurrentTaskLoggerFailure", func(t *testing.T) { // Create a writable temp dir for the main logger, then flip TMPDIR to a read-only // location so task-specific loggers fail to open. - writable := t.TempDir() - t.Setenv("TMPDIR", writable) + writable := setTempDirEnv(t, t.TempDir()) mainLogger, err := NewLoggerWithSuffix("shared-main") if err != nil { @@ -943,11 +941,11 @@ func TestExecutorExecuteConcurrentWithContextBranches(t *testing.T) { _ = os.Remove(mainLogger.Path()) }) - noWrite := filepath.Join(writable, "ro") - if err := os.Mkdir(noWrite, 0o500); err != nil { - t.Fatalf("failed to create read-only temp dir: %v", err) + notDir := filepath.Join(writable, "not-a-dir") + if err := os.WriteFile(notDir, []byte("x"), 0o644); err != nil { + t.Fatalf("failed to create temp file: %v", err) } - t.Setenv("TMPDIR", noWrite) + setTempDirEnv(t, notDir) taskA := nextExecutorTestTaskID("shared-a") taskB := nextExecutorTestTaskID("shared-b") @@ -1011,8 +1009,7 @@ func TestExecutorExecuteConcurrentWithContextBranches(t *testing.T) { }) t.Run("TestSanitizeTaskID", func(t *testing.T) { - tempDir := t.TempDir() - t.Setenv("TMPDIR", tempDir) + setTempDirEnv(t, t.TempDir()) orig := runCodexTaskFn runCodexTaskFn = func(task TaskSpec, timeout int) TaskResult { @@ -1081,8 +1078,7 @@ func TestExecutorSharedLogFalseWhenCustomLogPath(t *testing.T) { _ = devNull.Close() }) - tempDir := t.TempDir() - t.Setenv("TMPDIR", tempDir) + tempDir := setTempDirEnv(t, t.TempDir()) // Setup: 创建主 logger mainLogger, err := NewLoggerWithSuffix("shared-main") @@ -1098,11 +1094,11 @@ func TestExecutorSharedLogFalseWhenCustomLogPath(t *testing.T) { // 模拟场景:task logger 创建失败(通过设置只读的 TMPDIR), // 回退到主 logger(handle.shared=true), // 但 runCodexTaskFn 返回自定义的 LogPath(不等于主 logger 的路径) - roDir := filepath.Join(tempDir, "ro") - if err := os.Mkdir(roDir, 0o500); err != nil { - t.Fatalf("failed to create read-only dir: %v", err) + notDir := filepath.Join(tempDir, "not-a-dir") + if err := os.WriteFile(notDir, []byte("x"), 0o644); err != nil { + t.Fatalf("failed to create temp file: %v", err) } - t.Setenv("TMPDIR", roDir) + setTempDirEnv(t, notDir) orig := runCodexTaskFn customLogPath := "/custom/path/to.log" diff --git a/codeagent-wrapper/internal/app/main_integration_test.go b/codeagent-wrapper/internal/app/main_integration_test.go index 0b39a7c..79e8be5 100644 --- a/codeagent-wrapper/internal/app/main_integration_test.go +++ b/codeagent-wrapper/internal/app/main_integration_test.go @@ -725,20 +725,18 @@ func TestRunConcurrentSpeedupBenchmark(t *testing.T) { layers := [][]TaskSpec{tasks} serialStart := time.Now() - for _, task := range tasks { - _ = runCodexTaskFn(task, 5) - } + _ = executeConcurrentWithContext(nil, layers, 5, 1) serialElapsed := time.Since(serialStart) concurrentStart := time.Now() - _ = executeConcurrent(layers, 5) + _ = executeConcurrentWithContext(nil, layers, 5, 0) concurrentElapsed := time.Since(concurrentStart) - if concurrentElapsed >= serialElapsed/5 { - t.Fatalf("expected concurrent time <20%% of serial, serial=%v concurrent=%v", serialElapsed, concurrentElapsed) - } ratio := float64(concurrentElapsed) / float64(serialElapsed) t.Logf("speedup ratio (concurrent/serial)=%.3f", ratio) + if concurrentElapsed >= serialElapsed/2 { + t.Fatalf("expected concurrent time <50%% of serial, serial=%v concurrent=%v", serialElapsed, concurrentElapsed) + } } func TestRunStartupCleanupRemovesOrphansEndToEnd(t *testing.T) { diff --git a/codeagent-wrapper/internal/app/main_test.go b/codeagent-wrapper/internal/app/main_test.go index aebce67..3bf3da9 100644 --- a/codeagent-wrapper/internal/app/main_test.go +++ b/codeagent-wrapper/internal/app/main_test.go @@ -1948,7 +1948,7 @@ func TestRun_PassesReasoningEffortToTaskSpec(t *testing.T) { func TestRun_NoOutputMessage_ReturnsExitCode1AndWritesStderr(t *testing.T) { defer resetTestHooks() cleanupLogsFn = func() (CleanupStats, error) { return CleanupStats{}, nil } - t.Setenv("TMPDIR", t.TempDir()) + setTempDirEnv(t, t.TempDir()) selectBackendFn = func(name string) (Backend, error) { return testBackend{name: name, command: "echo"}, nil @@ -2099,8 +2099,7 @@ func TestRunBuildCodexArgs_ResumeMode_EmptySessionHandledGracefully(t *testing.T func TestRunBuildCodexArgs_BypassSandboxEnvTrue(t *testing.T) { defer resetTestHooks() - tempDir := t.TempDir() - t.Setenv("TMPDIR", tempDir) + setTempDirEnv(t, t.TempDir()) logger, err := NewLogger() if err != nil { @@ -2744,8 +2743,7 @@ func TestTailBufferWrite(t *testing.T) { func TestRunLogFunctions(t *testing.T) { defer resetTestHooks() - tempDir := t.TempDir() - t.Setenv("TMPDIR", tempDir) + setTempDirEnv(t, t.TempDir()) logger, err := NewLogger() if err != nil { @@ -2792,8 +2790,7 @@ func TestLoggerLogDropOnDone(t *testing.T) { func TestLoggerLogAfterClose(t *testing.T) { defer resetTestHooks() - tempDir := t.TempDir() - t.Setenv("TMPDIR", tempDir) + setTempDirEnv(t, t.TempDir()) logger, err := NewLogger() if err != nil { @@ -4243,8 +4240,7 @@ func TestRun_ExplicitStdinEmpty(t *testing.T) { func TestRun_ExplicitStdinReadError(t *testing.T) { defer resetTestHooks() - tempDir := t.TempDir() - t.Setenv("TMPDIR", tempDir) + tempDir := setTempDirEnv(t, t.TempDir()) logPath := filepath.Join(tempDir, fmt.Sprintf("codeagent-wrapper-%d.log", os.Getpid())) var logOutput string @@ -4340,8 +4336,7 @@ func TestRun_ExplicitStdinSuccess(t *testing.T) { func TestRun_PipedTaskReadError(t *testing.T) { defer resetTestHooks() - tempDir := t.TempDir() - t.Setenv("TMPDIR", tempDir) + tempDir := setTempDirEnv(t, t.TempDir()) logPath := filepath.Join(tempDir, fmt.Sprintf("codeagent-wrapper-%d.log", os.Getpid())) var logOutput string @@ -4394,8 +4389,7 @@ func TestRun_PipedTaskSuccess(t *testing.T) { func TestRun_LoggerLifecycle(t *testing.T) { defer resetTestHooks() - tempDir := t.TempDir() - t.Setenv("TMPDIR", tempDir) + tempDir := setTempDirEnv(t, t.TempDir()) logPath := filepath.Join(tempDir, fmt.Sprintf("codeagent-wrapper-%d.log", os.Getpid())) stdout := captureStdoutPipe() @@ -4443,8 +4437,7 @@ func TestRun_LoggerRemovedOnSignal(t *testing.T) { // Set shorter delays for faster test _ = executor.SetForceKillDelay(1) - tempDir := t.TempDir() - t.Setenv("TMPDIR", tempDir) + tempDir := setTempDirEnv(t, t.TempDir()) logPath := filepath.Join(tempDir, fmt.Sprintf("codeagent-wrapper-%d.log", os.Getpid())) scriptPath := filepath.Join(tempDir, "sleepy-codex.sh") @@ -4728,8 +4721,7 @@ func TestBackendRunCoverage(t *testing.T) { func TestParallelLogPathInSerialMode(t *testing.T) { defer resetTestHooks() - tempDir := t.TempDir() - t.Setenv("TMPDIR", tempDir) + tempDir := setTempDirEnv(t, t.TempDir()) os.Args = []string{"codeagent-wrapper", "do-stuff"} stdinReader = strings.NewReader("") diff --git a/codeagent-wrapper/internal/app/stdin_mode_test.go b/codeagent-wrapper/internal/app/stdin_mode_test.go index 8b659a9..9049c28 100644 --- a/codeagent-wrapper/internal/app/stdin_mode_test.go +++ b/codeagent-wrapper/internal/app/stdin_mode_test.go @@ -8,7 +8,7 @@ import ( func TestRunSingleMode_UseStdin_TargetArgAndTaskText(t *testing.T) { defer resetTestHooks() - t.Setenv("TMPDIR", t.TempDir()) + setTempDirEnv(t, t.TempDir()) logger, err := NewLogger() if err != nil { t.Fatalf("NewLogger(): %v", err) diff --git a/codeagent-wrapper/internal/executor/env_inject_test.go b/codeagent-wrapper/internal/executor/env_inject_test.go index 16d7cf1..089e379 100644 --- a/codeagent-wrapper/internal/executor/env_inject_test.go +++ b/codeagent-wrapper/internal/executor/env_inject_test.go @@ -36,10 +36,8 @@ func TestEnvInjectionWithAgent(t *testing.T) { t.Fatal(err) } - // Override HOME to use temp dir - oldHome := os.Getenv("HOME") - os.Setenv("HOME", tmpDir) - defer os.Setenv("HOME", oldHome) + t.Setenv("HOME", tmpDir) + t.Setenv("USERPROFILE", tmpDir) // Reset config cache config.ResetModelsConfigCacheForTest() @@ -104,9 +102,8 @@ func TestEnvInjectionLogic(t *testing.T) { t.Fatal(err) } - oldHome := os.Getenv("HOME") - os.Setenv("HOME", tmpDir) - defer os.Setenv("HOME", oldHome) + t.Setenv("HOME", tmpDir) + t.Setenv("USERPROFILE", tmpDir) config.ResetModelsConfigCacheForTest() defer config.ResetModelsConfigCacheForTest() diff --git a/codeagent-wrapper/internal/executor/env_stderr_test.go b/codeagent-wrapper/internal/executor/env_stderr_test.go index 0b9baa1..9c9c2b7 100644 --- a/codeagent-wrapper/internal/executor/env_stderr_test.go +++ b/codeagent-wrapper/internal/executor/env_stderr_test.go @@ -65,11 +65,8 @@ func TestEnvInjection_LogsToStderrAndMasksKey(t *testing.T) { t.Fatal(err) } - oldHome := os.Getenv("HOME") - if err := os.Setenv("HOME", tmpDir); err != nil { - t.Fatal(err) - } - defer func() { _ = os.Setenv("HOME", oldHome) }() + t.Setenv("HOME", tmpDir) + t.Setenv("USERPROFILE", tmpDir) config.ResetModelsConfigCacheForTest() defer config.ResetModelsConfigCacheForTest() diff --git a/codeagent-wrapper/internal/executor/executor.go b/codeagent-wrapper/internal/executor/executor.go index db5e8f9..14acccb 100644 --- a/codeagent-wrapper/internal/executor/executor.go +++ b/codeagent-wrapper/internal/executor/executor.go @@ -8,6 +8,7 @@ import ( "os" "os/exec" "os/signal" + "runtime" "sort" "strings" "sync" @@ -253,6 +254,15 @@ func (p *realProcess) Signal(sig os.Signal) error { // newCommandRunner creates a new commandRunner (test hook injection point) var newCommandRunner = func(ctx context.Context, name string, args ...string) commandRunner { + if runtime.GOOS == "windows" { + lowerName := strings.ToLower(strings.TrimSpace(name)) + if strings.HasSuffix(lowerName, ".bat") || strings.HasSuffix(lowerName, ".cmd") { + cmdArgs := make([]string, 0, 2+len(args)) + cmdArgs = append(cmdArgs, "/c", name) + cmdArgs = append(cmdArgs, args...) + return &realCmd{cmd: commandContext(ctx, "cmd.exe", cmdArgs...)} + } + } return &realCmd{cmd: commandContext(ctx, name, args...)} } @@ -1058,17 +1068,17 @@ func RunCodexTaskWithContext(parentCtx context.Context, taskSpec TaskSpec, backe } if envBackend != nil { - baseURL, apiKey := config.ResolveBackendConfig(cfg.Backend) - if agentName := strings.TrimSpace(taskSpec.Agent); agentName != "" { - agentBackend, _, _, _, agentBaseURL, agentAPIKey, _, err := config.ResolveAgentConfig(agentName) - if err == nil { - if strings.EqualFold(strings.TrimSpace(agentBackend), strings.TrimSpace(cfg.Backend)) { - baseURL, apiKey = agentBaseURL, agentAPIKey - } + baseURL, apiKey := config.ResolveBackendConfig(cfg.Backend) + if agentName := strings.TrimSpace(taskSpec.Agent); agentName != "" { + agentBackend, _, _, _, agentBaseURL, agentAPIKey, _, err := config.ResolveAgentConfig(agentName) + if err == nil { + if strings.EqualFold(strings.TrimSpace(agentBackend), strings.TrimSpace(cfg.Backend)) { + baseURL, apiKey = agentBaseURL, agentAPIKey } } - if injected := envBackend.Env(baseURL, apiKey); len(injected) > 0 { - cmd.SetEnv(injected) + } + if injected := envBackend.Env(baseURL, apiKey); len(injected) > 0 { + cmd.SetEnv(injected) // Log injected env vars with masked API keys (to file and stderr) for k, v := range injected { msg := fmt.Sprintf("Env: %s=%s", k, maskSensitiveValue(k, v)) diff --git a/codeagent-wrapper/internal/logger/logger_suffix_test.go b/codeagent-wrapper/internal/logger/logger_suffix_test.go index dffbfbd..48e6ec4 100644 --- a/codeagent-wrapper/internal/logger/logger_suffix_test.go +++ b/codeagent-wrapper/internal/logger/logger_suffix_test.go @@ -70,12 +70,11 @@ func TestLoggerWithSuffixNamingAndIsolation(t *testing.T) { func TestLoggerWithSuffixReturnsErrorWhenTempDirNotWritable(t *testing.T) { base := t.TempDir() - noWrite := filepath.Join(base, "ro") - if err := os.Mkdir(noWrite, 0o500); err != nil { - t.Fatalf("failed to create read-only temp dir: %v", err) + notDir := filepath.Join(base, "not-a-dir") + if err := os.WriteFile(notDir, []byte("x"), 0o644); err != nil { + t.Fatalf("failed to create temp file: %v", err) } - t.Cleanup(func() { _ = os.Chmod(noWrite, 0o700) }) - setTempDirEnv(t, noWrite) + setTempDirEnv(t, notDir) logger, err := NewLoggerWithSuffix("task-err") if err == nil { diff --git a/codeagent-wrapper/internal/logger/logger_test.go b/codeagent-wrapper/internal/logger/logger_test.go index 6061f1b..5fbe217 100644 --- a/codeagent-wrapper/internal/logger/logger_test.go +++ b/codeagent-wrapper/internal/logger/logger_test.go @@ -26,8 +26,7 @@ func compareCleanupStats(got, want CleanupStats) bool { } func TestLoggerCreatesFileWithPID(t *testing.T) { - tempDir := t.TempDir() - t.Setenv("TMPDIR", tempDir) + tempDir := setTempDirEnv(t, t.TempDir()) logger, err := NewLogger() if err != nil { @@ -46,8 +45,7 @@ func TestLoggerCreatesFileWithPID(t *testing.T) { } func TestLoggerWritesLevels(t *testing.T) { - tempDir := t.TempDir() - t.Setenv("TMPDIR", tempDir) + setTempDirEnv(t, t.TempDir()) logger, err := NewLogger() if err != nil { @@ -77,8 +75,7 @@ func TestLoggerWritesLevels(t *testing.T) { } func TestLoggerCloseStopsWorkerAndKeepsFile(t *testing.T) { - tempDir := t.TempDir() - t.Setenv("TMPDIR", tempDir) + setTempDirEnv(t, t.TempDir()) logger, err := NewLogger() if err != nil { @@ -104,8 +101,7 @@ func TestLoggerCloseStopsWorkerAndKeepsFile(t *testing.T) { } func TestLoggerConcurrentWritesSafe(t *testing.T) { - tempDir := t.TempDir() - t.Setenv("TMPDIR", tempDir) + setTempDirEnv(t, t.TempDir()) logger, err := NewLogger() if err != nil { @@ -390,12 +386,14 @@ func TestLoggerCleanupOldLogsPerformanceBound(t *testing.T) { fakePaths := make([]string, fileCount) for i := 0; i < fileCount; i++ { name := fmt.Sprintf("codeagent-wrapper-%d.log", 10000+i) - fakePaths[i] = createTempLog(t, tempDir, name) + fakePaths[i] = filepath.Join(tempDir, name) } stubGlobLogFiles(t, func(pattern string) ([]string, error) { return fakePaths, nil }) + stubFileStat(t, func(string) (os.FileInfo, error) { return fakeFileInfo{}, nil }) + stubEvalSymlinks(t, func(path string) (string, error) { return path, nil }) stubProcessRunning(t, func(int) bool { return false }) stubProcessStartTime(t, func(int) time.Time { return time.Time{} }) @@ -542,8 +540,7 @@ func TestLoggerIsUnsafeFileSecurityChecks(t *testing.T) { } func TestLoggerPathAndRemove(t *testing.T) { - tempDir := t.TempDir() - t.Setenv("TMPDIR", tempDir) + setTempDirEnv(t, t.TempDir()) logger, err := NewLoggerWithSuffix("sample") if err != nil {