/
t3
/
cli
Обзор
Документация
Войти
/
t3
/
cli
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
master
internal/controller/run.go
125 строк
4 KB
Ivan Shibkikh
fix logging issue
07 июл 2026, 11:03
07 июл 2026, 11:03
6f1602e
Код
Авторство
О чём код?
package controller import ( "context" "fmt" "io" "log/slog" "os" "path/filepath" "time" "gitverse.ru/t3/cli/internal/config" "gitverse.ru/t3/cli/pkg/console/output" ) func Run(ctx context.Context, logger *slog.Logger, f string) (retErr error) { workingDir, err := os.Getwd() if err != nil { return err } inputPath := f if !filepath.IsAbs(f) { inputPath = filepath.Join(workingDir, f) } inputPath, err = filepath.Abs(inputPath) if err != nil { return err } cfg, err := config.Load(inputPath) if err != nil { return err } // Resolve output directory: if relative, make absolute relative to working dir. outputDir := cfg.Output if !filepath.IsAbs(outputDir) { outputDir = filepath.Join(workingDir, outputDir) } // Create output directory if it doesn't exist. if err := os.MkdirAll(outputDir, 0755); err != nil { return fmt.Errorf("create output dir: %w", err) } if cfg.EnvFile != "" { err = cfg.SubstituteTokens(filepath.Join(filepath.Dir(inputPath), cfg.EnvFile)) if err != nil { return err } } bundle, err := Build(cfg, workingDir, inputPath, cfg.Script) if err != nil { return fmt.Errorf("cannot build project: %w", err) } // Open controller log file early so it captures setup() and teardown() output. // The file handle is reused by the orchestrator. var controllerLogFile *os.File if outputDir != "" { controllerLogFile, err = controllerLogWriter(outputDir) if err != nil { return fmt.Errorf("create controller log: %w", err) } } // Logger for setup() — writes to stdout only (no controller log file). // The plain-text output to controller.log is handled directly by the // bridge (console.log/error) via its controllerWriter parameter. var setupLogger *slog.Logger setupLogger = output.NewLogger(os.Stdout, slog.LevelDebug) // For teardown(), we write only to controller.log via the bridge's // controllerWriter mechanism. No stdout output needed since the test // is already complete and STDOUT belongs to the terminal. // The teardownLogger uses io.Discard because slog.Info() in the bridge // would duplicate to STDOUT; the bridge's controllerWriter already // writes directly to controllerLogFile. var teardownLogger *slog.Logger teardownLogger = output.NewLogger(io.Discard, slog.LevelDebug) setupResult, err := CallSetup(setupLogger, bundle, controllerLogFile) if err != nil { return fmt.Errorf("cannot call 'setup' function: %w", err) } // Substitute ${VAR} tokens in setup data from .env file if cfg.EnvFile != "" { setupResult = config.SubstituteSetupTokens(setupResult) } // Always call teardown after the test finishes (success or error). // This defer is placed here so it runs even if orch.Run fails or panics, // but NOT if setup() itself failed. // Also close the controller log file right after teardown, so teardown's // console.log output is captured in controller.log before it's closed. defer func() { if err := CallTeardown(teardownLogger, bundle, setupResult, controllerLogFile); err != nil { logger.Warn("teardown failed", "error", err) } if controllerLogFile != nil { controllerLogFile.Close() } }() startTime := time.Now() // Create a switchable stdout writer. // The writer initially outputs to stdout (plus TUI output when TUI starts). // After the TUI starts, stdout output is disabled to prevent flicker. stdoutWriter := newSwitchableWriter(os.Stdout) tuiLogger := output.NewLogger(stdoutWriter, slog.LevelDebug) orch := NewOrchestrator(tuiLogger, cfg, bundle, setupResult, outputDir, stdoutWriter.DisableStdout, controllerLogFile) orchErr := orch.Run(ctx) endTime := time.Now() // Always generate report after test completion. generateReport(logger, outputDir, orch.RunID(), cfg, startTime, endTime, orchErr, orch.FinalAgentMetrics()) return orchErr }