From 62c829fb12af9140a1c0283757d138268d2f7bdc Mon Sep 17 00:00:00 2001 From: DoomGuy <307901355+abhhjhh28-svg@users.noreply.github.com> Date: Thu, 15 Oct 2026 14:44:38 +0300 Subject: [PATCH] feat(sqlitetest): inject rollback failures --- dbkit/sqlite/go.mod | 2 +- dbkit/sqlite/go.sum | 4 +-- dbkit/sqlite/intx_test.go | 31 +++++++++++++++++++++++ dbkit/sqlite/sqlitetest/faults.go | 34 +++++++++++++++++++++++--- dbkit/sqlite/sqlitetest/faults_test.go | 23 +++++++++++++++++ 5 files changed, 87 insertions(+), 7 deletions(-) create mode 100644 dbkit/sqlite/intx_test.go diff --git a/dbkit/sqlite/go.mod b/dbkit/sqlite/go.mod index fba55e9..3c2e425 100644 --- a/dbkit/sqlite/go.mod +++ b/dbkit/sqlite/go.mod @@ -3,7 +3,7 @@ module github.com/gopherium/framework/dbkit/sqlite go 1.27.2 require ( - github.com/gopherium/framework/dbkit v0.1.0 + github.com/gopherium/framework/dbkit v0.3.0 github.com/pressly/goose/v3 v3.27.2 golang.org/x/sys v0.48.0 modernc.org/sqlite v1.60.1 diff --git a/dbkit/sqlite/go.sum b/dbkit/sqlite/go.sum index 4e79e97..205f60d 100644 --- a/dbkit/sqlite/go.sum +++ b/dbkit/sqlite/go.sum @@ -6,8 +6,8 @@ github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 h1:LMLX+LgTNWpfvCBdFe github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gopherium/framework/dbkit v0.1.0 h1:jeFld4uZu555PkgbZQRKkDK1WOOB8am+Wu2Ar4xX9Jw= -github.com/gopherium/framework/dbkit v0.1.0/go.mod h1:M837GTBK4MLEt0oqvEKhCQydKFNhVLWvsDlAcqT21co= +github.com/gopherium/framework/dbkit v0.3.0 h1:EkMU4dg6YkiIrn3SJH0oJ2KzLNIXrDMu4K+HHoraEIs= +github.com/gopherium/framework/dbkit v0.3.0/go.mod h1:sGsNkpbHe1LRW7k1qEjTzqbeHVYOnoCI5e1l/81TAvQ= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= diff --git a/dbkit/sqlite/intx_test.go b/dbkit/sqlite/intx_test.go new file mode 100644 index 0000000..04fca32 --- /dev/null +++ b/dbkit/sqlite/intx_test.go @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 + +package sqlite_test + +import ( + "database/sql" + "errors" + "testing" + + "github.com/gopherium/framework/dbkit" + "github.com/gopherium/framework/dbkit/sqlite/sqlitetest" +) + +func TestInTxJoinsBodyAndRollbackErrors(t *testing.T) { + t.Parallel() + + errBody := errors.New("sqlite test: the transaction body failed on purpose") + errRollback := errors.New("sqlite test: the rollback failed on purpose") + faults := &sqlitetest.Faults{} + db := sqlitetest.OpenWithFaults(t, testOptions(), faults) + faults.FailNextRollback(errRollback) + + err := dbkit.InTx(t.Context(), db, func(*sql.Tx) error { return errBody }) + + if !errors.Is(err, errBody) { + t.Errorf("InTx() error = %v, want it to match the body error", err) + } + if !errors.Is(err, errRollback) { + t.Errorf("InTx() error = %v, want it to match the rollback error", err) + } +} diff --git a/dbkit/sqlite/sqlitetest/faults.go b/dbkit/sqlite/sqlitetest/faults.go index 57b2be9..7cabced 100644 --- a/dbkit/sqlite/sqlitetest/faults.go +++ b/dbkit/sqlite/sqlitetest/faults.go @@ -11,12 +11,14 @@ import ( // Faults holds the failures the connections of a handle from OpenWithFaults answer with. type Faults struct { - // mu guards statements and commit. + // mu guards statements, commit and rollback. mu sync.Mutex // statements maps the exact text of each chosen statement to the error it answers with. statements map[string]error // commit is the error the next commit answers with, or nil. commit error + // rollback is the error the next rollback answers with, or nil. + rollback error } // FailStatement makes every statement whose text is exactly query answer err, inside transactions too. @@ -59,6 +61,22 @@ func (f *Faults) takeCommit() error { return err } +// FailNextRollback makes the next rollback end its transaction and answer err. +func (f *Faults) FailNextRollback(err error) { + f.mu.Lock() + defer f.mu.Unlock() + f.rollback = err +} + +// takeRollback returns the error the next rollback answers with and clears it. +func (f *Faults) takeRollback() error { + f.mu.Lock() + defer f.mu.Unlock() + err := f.rollback + f.rollback = nil + return err +} + // wrap returns c answering with the failures of f. func (f *Faults) wrap(c driver.Conn) driver.Conn { return &faultConn{driverConn: c.(driverConn), faults: f} @@ -169,12 +187,12 @@ func (s *faultStmt) QueryContext(ctx context.Context, args []driver.NamedValue) return s.driverStmt.QueryContext(ctx, args) } -// Begin starts a transaction whose commit answers with the next commit failure. +// Begin starts a transaction whose commit and rollback answer with the next matching failure. func (c *faultConn) Begin() (driver.Tx, error) { return c.BeginTx(context.Background(), driver.TxOptions{}) } -// BeginTx starts a transaction whose commit answers with the next commit failure. +// BeginTx starts a transaction whose commit and rollback answer with the next matching failure. func (c *faultConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) { tx, err := c.driverConn.BeginTx(ctx, opts) if err != nil { @@ -183,7 +201,7 @@ func (c *faultConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver. return &faultTx{Tx: tx, faults: c.faults}, nil } -// faultTx is a driver transaction whose commit answers with the next commit failure. +// faultTx is a driver transaction whose commit and rollback answer with the next matching failure. type faultTx struct { // Tx is the driver's own transaction. driver.Tx @@ -198,3 +216,11 @@ func (t *faultTx) Commit() error { } return t.Tx.Commit() } + +// Rollback ends the transaction and answers the next rollback failure, or the driver's answer when none is set. +func (t *faultTx) Rollback() error { + if err := t.faults.takeRollback(); err != nil { + return errors.Join(err, t.Tx.Rollback()) + } + return t.Tx.Rollback() +} diff --git a/dbkit/sqlite/sqlitetest/faults_test.go b/dbkit/sqlite/sqlitetest/faults_test.go index c51da44..c7994d2 100644 --- a/dbkit/sqlite/sqlitetest/faults_test.go +++ b/dbkit/sqlite/sqlitetest/faults_test.go @@ -164,6 +164,29 @@ func TestFaultFailsTheChosenCommit(t *testing.T) { mustCount(t, db, 1) } +func TestFaultFailsTheChosenRollback(t *testing.T) { + t.Parallel() + + faults := &sqlitetest.Faults{} + db := faultOpen(t, faults) + faults.FailNextRollback(errInjected) + tx := mustBegin(t, db) + mustExec(t, tx, faultInsert) + + err := tx.Rollback() + + if !errors.Is(err, errInjected) { + t.Errorf("Rollback() error = %v, want the injected error", err) + } + mustCount(t, db, 0) + next := mustBegin(t, db) + mustExec(t, next, "INSERT INTO fault_rows VALUES (2)") + if err := next.Rollback(); err != nil { + t.Errorf("the next Rollback() error = %v, want nil once the fault answered", err) + } + mustCount(t, db, 0) +} + func TestFaultFailsTheChosenStatementInsideATransaction(t *testing.T) { t.Parallel()