Files
orm/tx_panic_test.go
zeekay 992d08f37c refactor(orm): purge Postgres — SQLite only
Delete db/sql.go + db/sql_query.go (PG-specific: pg_advisory_xact_lock,
IsolationReadCommitted, 40001/40P01 retry). SQLite BEGIN IMMEDIATE gives
us linearizable writes without PG primitives.

adapter.go simplified to SQLite-only transaction model. Retry loop
re-scoped to SQLITE_BUSY with exponential backoff.

Tests green: hanzoai/orm, /db, /engine all pass.
2026-04-18 21:07:55 -07:00

61 lines
1.8 KiB
Go

package orm
import (
"context"
"strings"
"testing"
)
// R3-7: RunInTransactionWith must recover from panics inside the
// callback, roll back cleanly at the driver layer, and re-panic so the
// caller's goroutine trace shows the original defect. A swallowed panic
// would leak DB connections and silently corrupt application state.
func TestRunInTransactionWith_PanicRecovered(t *testing.T) {
db := newTestSQLite(t)
defer func() {
r := recover()
if r == nil {
t.Fatal("expected panic to propagate to caller")
}
msg, ok := r.(string)
if !ok {
t.Fatalf("panic value not string: %T %v", r, r)
}
if !strings.Contains(msg, "intentional R3-7") {
t.Fatalf("panic value lost: %q", msg)
}
}()
_ = db.RunInTransactionWith(context.Background(), &TxOptions{
Isolation: IsolationDefault,
}, func(tx DB) error {
panic("intentional R3-7 test panic")
})
t.Fatal("RunInTransactionWith must re-panic; we should not reach here")
}
// R3-7: a panicking callback returns ErrTxPanic to the retry loop (not a
// serialization failure) so the loop exits immediately instead of
// retrying endlessly. In practice the re-panic fires before the retry
// loop even sees the error, but this test reaches into the guarded
// helper directly to verify the sentinel path.
func TestRunAttempt_PanicReturnsErrTxPanic(t *testing.T) {
// The defer catches the re-panic that runAttempt issues after it
// records the panic and rolls back.
defer func() {
_ = recover()
}()
db := newTestSQLite(t).(*dbAdapter)
err := db.runAttempt(context.Background(), 0, nil, func(tx DB) error {
panic("R3-7 sentinel")
})
// unreachable in practice; if the re-panic is somehow skipped we
// at least assert the sentinel is returned.
if err != nil && err != ErrTxPanic {
t.Errorf("expected ErrTxPanic, got %v", err)
}
}