diff --git a/integration-tests/tests/doctor_fix_shows_remediation.txtar b/integration-tests/tests/doctor_fix_shows_remediation.txtar new file mode 100644 index 0000000000..e03d9c29e1 --- /dev/null +++ b/integration-tests/tests/doctor_fix_shows_remediation.txtar @@ -0,0 +1,11 @@ +# Test that cog doctor --fix prints the remediation text for unfixed findings. + +! cog doctor --fix +stderr 'predict.py not found' +stderr 'Remediation: Create predict.py or update the predict field in cog.yaml' +! stderr '(no auto-fix available)' + +-- cog.yaml -- +build: + python_version: "3.12" +predict: "predict.py:Predictor" diff --git a/pkg/cli/doctor.go b/pkg/cli/doctor.go index b367c53d53..62a6e114b7 100644 --- a/pkg/cli/doctor.go +++ b/pkg/cli/doctor.go @@ -151,8 +151,12 @@ func printDoctorResults(result *doctor.Result, fix bool, hasFixableErrors bool) } console.Infof(" %s%s", location, f.Message) - if fix && !cr.Fixed && f.Remediation != "" { - console.Infof(" (no auto-fix available)") + if fix && !cr.Fixed { + if f.Remediation != "" { + console.Infof(" Remediation: %s", f.Remediation) + } else { + console.Infof(" (no auto-fix available)") + } } } } diff --git a/pkg/cli/doctor_test.go b/pkg/cli/doctor_test.go new file mode 100644 index 0000000000..b2df686656 --- /dev/null +++ b/pkg/cli/doctor_test.go @@ -0,0 +1,98 @@ +package cli + +import ( + "io" + "os" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/replicate/cog/pkg/doctor" + "github.com/replicate/cog/pkg/util/console" +) + +func TestPrintDoctorResultsFixShowsRemediationForUnfixedFinding(t *testing.T) { + result := &doctor.Result{ + Results: []doctor.CheckResult{ + { + Check: unfixedDoctorCheck{}, + Findings: []doctor.Finding{ + { + Severity: doctor.SeverityError, + Message: "predict.py not found", + Remediation: `Create predict.py or update the predict field in cog.yaml`, + File: "cog.yaml", + Line: 3, + }, + }, + }, + }, + } + + output := captureDoctorStderr(t, func() { + console.SetColor(false) + printDoctorResults(result, true, false) + }) + + require.Contains(t, output, "predict.py not found") + require.Contains(t, output, "Remediation: Create predict.py or update the predict field in cog.yaml") + require.NotContains(t, output, "(no auto-fix available)") +} + +func TestPrintDoctorResultsFixShowsNoAutoFixWhenRemediationMissing(t *testing.T) { + result := &doctor.Result{ + Results: []doctor.CheckResult{ + { + Check: unfixedDoctorCheck{}, + Findings: []doctor.Finding{ + { + Severity: doctor.SeverityWarning, + Message: "something could not be fixed", + }, + }, + }, + }, + } + + output := captureDoctorStderr(t, func() { + console.SetColor(false) + printDoctorResults(result, true, false) + }) + + require.Contains(t, output, "(no auto-fix available)") +} + +type unfixedDoctorCheck struct{} + +func (unfixedDoctorCheck) Name() string { return "unfixed-check" } +func (unfixedDoctorCheck) Group() doctor.Group { return doctor.GroupConfig } +func (unfixedDoctorCheck) Description() string { return "Unfixed check" } +func (unfixedDoctorCheck) Check(*doctor.CheckContext) ([]doctor.Finding, error) { + return nil, nil +} +func (unfixedDoctorCheck) Fix(*doctor.CheckContext, []doctor.Finding) error { + return doctor.ErrNoAutoFix +} + +func captureDoctorStderr(t *testing.T, fn func()) string { + t.Helper() + + originalStderr := os.Stderr + r, w, err := os.Pipe() + require.NoError(t, err) + + os.Stderr = w + defer func() { + os.Stderr = originalStderr + }() + + fn() + + require.NoError(t, w.Close()) + out, err := io.ReadAll(r) + require.NoError(t, err) + require.NoError(t, r.Close()) + + return strings.ReplaceAll(string(out), "\r\n", "\n") +}