TestMain Without os.Exit

ID

go.testmain_no_exit

Severity

low

Remediation Complexity

simple

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

Go

Tags

CWE:391, reliability, testing

Description

Reports a TestMain(m *testing.M) entry point whose body never calls os.Exit. Without that call the process always exits with status zero, so failing tests do not fail the build.

Rationale

When a test binary defines TestMain, that function replaces the default runner and becomes solely responsible for translating the result of m.Run() into the process exit code. If it returns normally instead of calling os.Exit(m.Run()), the process exits 0 no matter how many tests failed — the failures are silently swallowed and continuous integration reports a green build on a red test suite. The check fires only on a function named TestMain that takes a *testing.M and contains no os.Exit call anywhere in its body.

func TestMain(m *testing.M) { // FLAW — never calls os.Exit; failures are hidden
    m.Run()
}

func TestMain(m *testing.M) { // OK
    os.Exit(m.Run())
}

Remediation

Call os.Exit(m.Run()) so the test result becomes the process exit code, running any required setup before and teardown after.