Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
name: CI

env:
# Database to connect to that can create other databases with `CREATE DATABASE`.
ADMIN_DATABASE_URL: postgres://postgres:postgres@localhost:5432

TEST_DATABASE_URL: postgres://postgres:postgres@localhost:5432/river_test?pool_max_conns=15&sslmode=disable

on:
push:
branches:
Expand All @@ -10,6 +16,19 @@ jobs:
build_and_test:
runs-on: ubuntu-latest

services:
postgres:
image: postgres:17
env:
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 2s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432

steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -23,6 +42,9 @@ jobs:
- name: Display Go version
run: go version

- name: Set up database
run: psql -c "CREATE DATABASE river_test" $ADMIN_DATABASE_URL

- name: Test
run: make test

Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- More complete example test for `nilerror` package. [PR #27](https://github.com/riverqueue/rivercontrib/pull/27).

## [0.5.0] - 2025-05-02

### Added
Expand Down
77 changes: 69 additions & 8 deletions nilerror/example_hook_test.go
Original file line number Diff line number Diff line change
@@ -1,31 +1,92 @@
package nilerror_test

import (
"context"
"log/slog"

"github.com/jackc/pgx/v5/pgxpool"
"github.com/rivercontrib/nilerror"

"github.com/riverqueue/river"
"github.com/riverqueue/river/riverdbtest"
"github.com/riverqueue/river/riverdriver/riverpgxv5"
"github.com/riverqueue/river/rivershared/riversharedtest"
"github.com/riverqueue/river/rivershared/util/slogutil"
"github.com/riverqueue/river/rivershared/util/testutil"
"github.com/riverqueue/river/rivertype"
)

type CustomError struct{}

func (*CustomError) Error() string {
return "my custom error"
}

type CustomErrorArgs struct{}

func (CustomErrorArgs) Kind() string { return "custom_error" }

type CustomErrorWorker struct {
river.WorkerDefaults[CustomErrorArgs]
}

func (w *CustomErrorWorker) Work(ctx context.Context, job *river.Job[CustomErrorArgs]) error {
var customErr *CustomError // nil error, but non-nil when wrapped in an error interface
return customErr
}

func ExampleHook() {
_, err := river.NewClient(riverpgxv5.New(nil), &river.Config{
Hooks: []rivertype.Hook{
// Install a nilerror check that will return an error when it
// detects a nil struct wrapped in a non-nil error interface.
nilerror.NewHook(nil),
ctx := context.Background()

dbPool, err := pgxpool.New(ctx, riversharedtest.TestDatabaseURL())
if err != nil {
panic(err)
}
defer dbPool.Close()

// Alternatively, suppress errors and produce warning logging.
workers := river.NewWorkers()
river.AddWorker(workers, &CustomErrorWorker{})

riverClient, err := river.NewClient(riverpgxv5.New(dbPool), &river.Config{
Hooks: []rivertype.Hook{
// Suppress option prevents errors in favor of warning logging when
// a nil struct wrapped in a non-nil error interface is detected.
nilerror.NewHook(&nilerror.HookConfig{Suppress: true}),

// Alternatively, return an error and fail jobs instead.
// nilerror.NewHook(nil),
},
Logger: slog.New(&slogutil.SlogMessageOnlyHandler{Level: slog.LevelWarn}),
Queues: map[string]river.QueueConfig{
river.QueueDefault: {MaxWorkers: 100},
},
Logger: slog.New(&slogutil.SlogMessageOnlyHandler{Level: slog.LevelWarn}),
TestOnly: true, // suitable only for use in tests; remove for live environments
Schema: riverdbtest.TestSchema(ctx, testutil.PanicTB(), riverpgxv5.New(dbPool), nil), // only necessary for the example test
TestOnly: true, // suitable only for use in tests; remove for live environments
Workers: workers,
})
if err != nil {
panic(err)
}

// Out of example scope, but used to wait until a job is worked.
subscribeChan, subscribeCancel := riverClient.Subscribe(river.EventKindJobCompleted)
defer subscribeCancel()

if _, err = riverClient.Insert(ctx, CustomErrorArgs{}, nil); err != nil {
panic(err)
}

if err := riverClient.Start(ctx); err != nil {
panic(err)
}

// Wait for jobs to complete. Only needed for purposes of the example test.
riversharedtest.WaitOrTimeoutN(testutil.PanicTB(), subscribeChan, 1)

if err := riverClient.Stop(ctx); err != nil {
panic(err)
}

// Output:
// nilerror.Hook: Got non-nil error containing nil internal value (see: https://go.dev/doc/faq#nil_error); probably a bug: (*nilerror_test.CustomError)(<nil>)
}
2 changes: 1 addition & 1 deletion nilerror/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func (h *Hook) WorkEnd(ctx context.Context, err error) error {
lastSlash = strings.LastIndex(packagePath, "/")
packageName = packagePath[lastSlash+1:]
nilPtrName = fmt.Sprintf("(*%s.%s)(<nil>)", packageName, nonPtrType.Name())
message = fmt.Sprintf("non-nil error containing nil internal value (see: https://go.dev/doc/faq#nil_error); probably a bug: %s", nilPtrName)
message = "non-nil error containing nil internal value (see: https://go.dev/doc/faq#nil_error); probably a bug: " + nilPtrName
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Linter did this automatically.

)

if h.config.Suppress {
Expand Down
8 changes: 4 additions & 4 deletions nilerror/hook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ package nilerror

import (
"bytes"
"context"
"log/slog"
"testing"

"github.com/stretchr/testify/require"

"github.com/riverqueue/river/rivershared/baseservice"
"github.com/riverqueue/river/rivershared/riversharedtest"
"github.com/riverqueue/river/rivershared/util/slogutil"
"github.com/riverqueue/river/rivertype"
"github.com/stretchr/testify/require"
)

// Verify interface compliance.
Expand All @@ -27,7 +27,7 @@ func (*myCustomError) Error() string {
func TestHook(t *testing.T) {
t.Parallel()

ctx := context.Background()
ctx := t.Context()

type testBundle struct{}

Expand Down Expand Up @@ -80,7 +80,7 @@ func TestHook(t *testing.T) {
hook, _ := setupConfig(t, &HookConfig{Suppress: true})

var logBuf bytes.Buffer
hook.Archetype.Logger = slog.New(&slogutil.SlogMessageOnlyHandler{Level: slog.LevelWarn, Out: &logBuf})
hook.Logger = slog.New(&slogutil.SlogMessageOnlyHandler{Level: slog.LevelWarn, Out: &logBuf})
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same with this one.


var myCustomErr *myCustomError
require.NoError(t, hook.WorkEnd(ctx, myCustomErr))
Expand Down
Loading