Files
bootstrap_dev_env/issues_test.go
JMR-devandCopilot de7f71fea6 Suppress intentional errLog output during tests
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>
2026-05-26 14:55:51 -05:00

135 lines
2.9 KiB
Go

package main
import (
"bytes"
"io"
"os"
"strings"
"testing"
)
func TestIssuesLogging(t *testing.T) {
defer resetMocks()
resetMocks()
// Capture issue log output via the injectable writer.
var buf bytes.Buffer
oldWriter := issueLogWriter
issueLogWriter = &buf
defer func() { issueLogWriter = oldWriter }()
warn("something is deprecated")
errLog("something failed")
notice("please restart shell")
output := buf.String()
if !strings.Contains(output, "[WARN] something is deprecated") {
t.Errorf("output missing warning: %q", output)
}
if !strings.Contains(output, "[ERROR] something failed") {
t.Errorf("output missing error: %q", output)
}
issuesMu.Lock()
issueLen := len(issues)
noticeLen := len(notices)
errCount := errorCount
issuesMu.Unlock()
if issueLen != 2 {
t.Errorf("expected 2 logged issues, got %d", issueLen)
}
if noticeLen != 1 {
t.Errorf("expected 1 notice, got %d", noticeLen)
}
if errCount != 1 {
t.Errorf("expected 1 error count, got %d", errCount)
}
if !hasErrors() {
t.Error("expected hasErrors() to return true after errLog call")
}
}
func TestWriteRunLog(t *testing.T) {
defer resetMocks()
tmp := t.TempDir()
_ = tmp
// Redirect path function or mock executable path
// In issues.go, we can define a package variable to override runLogPath if we want,
// or we can mock osWriteFile. Since we mocked osWriteFile, let's use that!
var writtenPath string
var writtenData []byte
osWriteFile = func(path string, data []byte, perm os.FileMode) error {
writtenPath = path
writtenData = data
return nil
}
// No issues case
writeRunLog()
if writtenPath != "" {
t.Error("expected run log not to be written when there are no issues")
}
// Add an issue
warn("test warning")
writeRunLog()
if writtenPath == "" {
t.Fatal("expected run log to be written")
}
if !strings.Contains(string(writtenData), "[WARN] test warning") {
t.Errorf("expected log to contain the warning, got: %s", string(writtenData))
}
}
func TestHasErrors(t *testing.T) {
defer resetMocks()
if hasErrors() {
t.Error("expected hasErrors() false with no errors logged")
}
warn("just a warning")
if hasErrors() {
t.Error("expected hasErrors() false after only a warning")
}
errLog("a real error")
if !hasErrors() {
t.Error("expected hasErrors() true after errLog call")
}
}
func TestPrintNotices(t *testing.T) {
defer resetMocks()
// Capture stdout
oldStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
printNotices() // Should be empty
notice("first notice")
notice("second notice")
printNotices()
w.Close()
os.Stdout = oldStdout
var buf bytes.Buffer
io.Copy(&buf, r)
output := buf.String()
if !strings.Contains(output, "Notices:") {
t.Error("stdout missing notices header")
}
if !strings.Contains(output, "first notice") || !strings.Contains(output, "second notice") {
t.Errorf("stdout missing notice contents: %q", output)
}
}