Tests that exercise error paths in errLog/warn were causing GitHub Actions to annotate test output with 'Error: R] ...' (GHA eats '[ERRO' from '[ERROR]' lines and surfaces the rest as workflow errors), making real failures hard to distinguish from expected behavior. - issues.go: route logIssue output through issueLogWriter (io.Writer, defaults to os.Stdout). errorCount/issues/notices recording is unchanged so tests still assert via errorCount. - testmain_test.go: TestMain sets issueLogWriter = io.Discard for the whole test binary. - issues_test.go: TestIssuesLogging temporarily redirects issueLogWriter to a bytes.Buffer (replacing os.Pipe stdout capture). go test -v ./... now contains zero stray '[ERROR]' lines; real test failures remain visible and 'go test' still exits non-zero on failure, failing the workflow. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
89 lines
1.9 KiB
Go
89 lines
1.9 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// Issue log accumulated over the run; written to bootstrap_run.log at the end
|
|
// when there's something to report.
|
|
|
|
var (
|
|
issuesMu sync.Mutex
|
|
issues []string
|
|
notices []string
|
|
errorCount int
|
|
// issueLogWriter is the destination for human-facing issue log lines.
|
|
// Overridden during tests to suppress intentional error-path output.
|
|
issueLogWriter io.Writer = os.Stdout
|
|
)
|
|
|
|
func logIssue(level, msg string) {
|
|
issuesMu.Lock()
|
|
defer issuesMu.Unlock()
|
|
fmt.Fprintf(issueLogWriter, " [%s] %s\n", level, msg)
|
|
issues = append(issues, fmt.Sprintf("[%s] %s", level, msg))
|
|
if level == "ERROR" {
|
|
errorCount++
|
|
}
|
|
}
|
|
|
|
func warn(msg string) { logIssue("WARN", msg) }
|
|
func errLog(msg string) { logIssue("ERROR", msg) }
|
|
|
|
func hasErrors() bool {
|
|
issuesMu.Lock()
|
|
defer issuesMu.Unlock()
|
|
return errorCount > 0
|
|
}
|
|
|
|
func notice(msg string) {
|
|
issuesMu.Lock()
|
|
defer issuesMu.Unlock()
|
|
notices = append(notices, msg)
|
|
}
|
|
|
|
func runLogPath() string {
|
|
exe, err := os.Executable()
|
|
if err != nil {
|
|
return "bootstrap_run.log"
|
|
}
|
|
return filepath.Join(filepath.Dir(exe), "bootstrap_run.log")
|
|
}
|
|
|
|
func writeRunLog() {
|
|
issuesMu.Lock()
|
|
defer issuesMu.Unlock()
|
|
if len(issues) == 0 {
|
|
fmt.Println("\nNo issues — log file not written.")
|
|
return
|
|
}
|
|
path := runLogPath()
|
|
ts := time.Now().Format("2006-01-02 15:04:05")
|
|
lines := []string{fmt.Sprintf("# Bootstrap run — %s", ts), ""}
|
|
lines = append(lines, issues...)
|
|
content := strings.Join(lines, "\n") + "\n"
|
|
if err := osWriteFile(path, []byte(content), 0o644); err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to write run log: %v\n", err)
|
|
return
|
|
}
|
|
fmt.Printf("\n%d issue(s) logged to: %s\n", len(issues), path)
|
|
}
|
|
|
|
func printNotices() {
|
|
issuesMu.Lock()
|
|
defer issuesMu.Unlock()
|
|
if len(notices) == 0 {
|
|
return
|
|
}
|
|
fmt.Println("\nNotices:")
|
|
for _, n := range notices {
|
|
fmt.Printf(" • %s\n", n)
|
|
}
|
|
}
|