Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package forgejo-runner for openSUSE:Factory checked in at 2026-04-10 17:52:52 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/forgejo-runner (Old) and /work/SRC/openSUSE:Factory/.forgejo-runner.new.21863 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "forgejo-runner" Fri Apr 10 17:52:52 2026 rev:44 rq:1345680 version:12.8.2 Changes: -------- --- /work/SRC/openSUSE:Factory/forgejo-runner/forgejo-runner.changes 2026-04-08 17:15:13.678681485 +0200 +++ /work/SRC/openSUSE:Factory/.forgejo-runner.new.21863/forgejo-runner.changes 2026-04-10 18:02:04.036270795 +0200 @@ -1,0 +2,6 @@ +Thu Apr 9 19:05:39 UTC 2026 - Richard Rahl <[email protected]> + +- Update to version 12.8.2: + * fix: return error when one-job receives no task + +------------------------------------------------------------------- Old: ---- forgejo-runner-12.8.1.obscpio New: ---- forgejo-runner-12.8.2.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ forgejo-runner.spec ++++++ --- /var/tmp/diff_new_pack.0sIe14/_old 2026-04-10 18:02:05.228319876 +0200 +++ /var/tmp/diff_new_pack.0sIe14/_new 2026-04-10 18:02:05.232320042 +0200 @@ -18,7 +18,7 @@ %define services %{name}.service Name: forgejo-runner -Version: 12.8.1 +Version: 12.8.2 Release: 0 Summary: Daemon that connects to a Forgejo instance and runs CI jobs License: GPL-3.0-or-later ++++++ _service ++++++ --- /var/tmp/diff_new_pack.0sIe14/_old 2026-04-10 18:02:05.284322183 +0200 +++ /var/tmp/diff_new_pack.0sIe14/_new 2026-04-10 18:02:05.292322512 +0200 @@ -2,7 +2,7 @@ <service name="obs_scm" mode="manual"> <param name="url">https://code.forgejo.org/forgejo/runner</param> <param name="scm">git</param> - <param name="revision">refs/tags/v12.8.1</param> + <param name="revision">refs/tags/v12.8.2</param> <param name="versionformat">@PARENT_TAG@</param> <param name="changesgenerate">disable</param> <param name="versionrewrite-pattern">v(.*)</param> ++++++ forgejo-runner-12.8.1.obscpio -> forgejo-runner-12.8.2.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.8.1/internal/app/cmd/cmd.go new/forgejo-runner-12.8.2/internal/app/cmd/cmd.go --- old/forgejo-runner-12.8.1/internal/app/cmd/cmd.go 2026-04-06 19:20:18.000000000 +0200 +++ new/forgejo-runner-12.8.2/internal/app/cmd/cmd.go 2026-04-07 17:51:41.000000000 +0200 @@ -5,9 +5,11 @@ import ( "context" + "errors" "fmt" "os" + "code.forgejo.org/forgejo/runner/v12/internal/app/poll" "github.com/spf13/cobra" "code.forgejo.org/forgejo/runner/v12/internal/pkg/config" @@ -106,6 +108,10 @@ rootCmd.CompletionOptions.HiddenDefaultCmd = true if err := rootCmd.Execute(); err != nil { + if errors.Is(err, poll.ErrNoTaskReceived) { + os.Exit(2) + } + os.Exit(1) } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.8.1/internal/app/cmd/job.go new/forgejo-runner-12.8.2/internal/app/cmd/job.go --- old/forgejo-runner-12.8.1/internal/app/cmd/job.go 2026-04-06 19:20:18.000000000 +0200 +++ new/forgejo-runner-12.8.2/internal/app/cmd/job.go 2026-04-07 17:51:41.000000000 +0200 @@ -89,6 +89,11 @@ } poller := newSingleTaskPoller(ctx, cfg, client, runner, args.wait, args.GetHandle()) - poller.Poll() - return poller.Shutdown(ctx) + err = poller.Poll() + if shutdownErr := poller.Shutdown(ctx); shutdownErr != nil { + // If the shutdown error was returned, then context cancellation or a timeout would result in an exit code that + // indicates an error. + log.Warnf("error during poller shutdown: %s", shutdownErr) + } + return err } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.8.1/internal/app/cmd/job_test.go new/forgejo-runner-12.8.2/internal/app/cmd/job_test.go --- old/forgejo-runner-12.8.1/internal/app/cmd/job_test.go 2026-04-06 19:20:18.000000000 +0200 +++ new/forgejo-runner-12.8.2/internal/app/cmd/job_test.go 2026-04-07 17:51:41.000000000 +0200 @@ -12,6 +12,7 @@ runnerv1 "code.forgejo.org/forgejo/actions-proto/runner/v1" "code.forgejo.org/forgejo/runner/v12/act/cacheproxy" + "code.forgejo.org/forgejo/runner/v12/internal/app/poll" "code.forgejo.org/forgejo/runner/v12/internal/app/run" mock_runner "code.forgejo.org/forgejo/runner/v12/internal/app/run/mocks" "code.forgejo.org/forgejo/runner/v12/internal/pkg/client" @@ -82,7 +83,62 @@ close(runJobCompleted) }() - mockRunner.On("Run", mock.Anything, mock.Anything) + // Wait for the goroutine that executes runJob() to stop. + <-runJobCompleted +} + +func TestRunJob_ErrorWhenNoTaskReceived(t *testing.T) { + rawConfig := ` +cache: + enabled: false +server: + connections: + example: + url: https://example.com/forgejo + uuid: 41414141-4141-4141-4141-414141414141 + token: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +` + + tempDir := t.TempDir() + configPath := filepath.Join(tempDir, "config.yaml") + + err := os.WriteFile(configPath, []byte(rawConfig), 0o644) + require.NoError(t, err) + + mockClient := mock_client.NewMockClient(t) + mockClient. + On("Address").Return("https://example.com/forgejo"). + On("SetRequestKey", mock.Anything).Return(func() {}). + On("FetchInterval").Return(time.Millisecond). + On("FetchTask", mock.Anything, connect.NewRequest(&runnerv1.FetchTaskRequest{})). + Return(connect.NewResponse(&runnerv1.FetchTaskResponse{Task: nil, TasksVersion: int64(1)}), nil) + + mockRunner := mock_runner.NewMockRunner(t) + + defer testutils.MockVariable(&initLogging, func(cfg *config.Config) {})() + defer testutils.MockVariable(&createClient, func(cfg *config.Config, conn *config.Connection) client.Client { + assert.Equal(t, "https://example.com/forgejo", conn.URL.String()) + assert.Equal(t, "41414141-4141-4141-4141-414141414141", conn.UUID.String()) + assert.Equal(t, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", conn.Token) + + return mockClient + })() + defer testutils.MockVariable(&createRunner, func(ctx context.Context, name string, cfg *config.Config, cli client.Client, ls labels.Labels, cacheProxy *cacheproxy.Handler) (run.RunnerInterface, string, bool, error) { + if name == "example" { + return mockRunner, "example", false, nil + } + t.Fatalf("unexpected connection name: %q", name) + return nil, "", false, nil + })() + + runJobCompleted := make(chan any) + go func() { + err := runJob(t.Context(), &configPath, &runJobArgs{}) + require.ErrorIs(t, err, poll.ErrNoTaskReceived) + + // Signal that runJob() has completed. + close(runJobCompleted) + }() // Wait for the goroutine that executes runJob() to stop. <-runJobCompleted @@ -152,8 +208,6 @@ close(runJobCompleted) }() - mockRunner.On("Run", mock.Anything, mock.Anything) - // Wait for the goroutine that executes runJob() to stop. <-runJobCompleted } @@ -216,8 +270,6 @@ close(runJobCompleted) }() - mockRunner.On("Run", mock.Anything, mock.Anything) - // Wait for the goroutine that executes runJob() to stop. <-runJobCompleted } @@ -280,8 +332,6 @@ close(runJobCompleted) }() - mockRunner.On("Run", mock.Anything, mock.Anything) - // Wait for the goroutine that executes runJob() to stop. <-runJobCompleted } @@ -346,8 +396,6 @@ close(runJobCompleted) }() - mockRunner.On("Run", mock.Anything, mock.Anything) - // Wait for the goroutine that executes runJob() to stop. <-runJobCompleted } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.8.1/internal/app/poll/mocks/single.go new/forgejo-runner-12.8.2/internal/app/poll/mocks/single.go --- old/forgejo-runner-12.8.1/internal/app/poll/mocks/single.go 1970-01-01 01:00:00.000000000 +0100 +++ new/forgejo-runner-12.8.2/internal/app/poll/mocks/single.go 2026-04-07 17:51:41.000000000 +0200 @@ -0,0 +1,134 @@ +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify + +package mocks + +import ( + "context" + + mock "github.com/stretchr/testify/mock" +) + +// NewMockSingleTaskPoller creates a new instance of MockSingleTaskPoller. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockSingleTaskPoller(t interface { + mock.TestingT + Cleanup(func()) +}, +) *MockSingleTaskPoller { + mock := &MockSingleTaskPoller{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// MockSingleTaskPoller is an autogenerated mock type for the SingleTaskPoller type +type MockSingleTaskPoller struct { + mock.Mock +} + +type MockSingleTaskPoller_Expecter struct { + mock *mock.Mock +} + +func (_m *MockSingleTaskPoller) EXPECT() *MockSingleTaskPoller_Expecter { + return &MockSingleTaskPoller_Expecter{mock: &_m.Mock} +} + +// Poll provides a mock function for the type MockSingleTaskPoller +func (_mock *MockSingleTaskPoller) Poll() error { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for Poll") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func() error); ok { + r0 = returnFunc() + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockSingleTaskPoller_Poll_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Poll' +type MockSingleTaskPoller_Poll_Call struct { + *mock.Call +} + +// Poll is a helper method to define mock.On call +func (_e *MockSingleTaskPoller_Expecter) Poll() *MockSingleTaskPoller_Poll_Call { + return &MockSingleTaskPoller_Poll_Call{Call: _e.mock.On("Poll")} +} + +func (_c *MockSingleTaskPoller_Poll_Call) Run(run func()) *MockSingleTaskPoller_Poll_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockSingleTaskPoller_Poll_Call) Return(err error) *MockSingleTaskPoller_Poll_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockSingleTaskPoller_Poll_Call) RunAndReturn(run func() error) *MockSingleTaskPoller_Poll_Call { + _c.Call.Return(run) + return _c +} + +// Shutdown provides a mock function for the type MockSingleTaskPoller +func (_mock *MockSingleTaskPoller) Shutdown(ctx context.Context) error { + ret := _mock.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Shutdown") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = returnFunc(ctx) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockSingleTaskPoller_Shutdown_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Shutdown' +type MockSingleTaskPoller_Shutdown_Call struct { + *mock.Call +} + +// Shutdown is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockSingleTaskPoller_Expecter) Shutdown(ctx interface{}) *MockSingleTaskPoller_Shutdown_Call { + return &MockSingleTaskPoller_Shutdown_Call{Call: _e.mock.On("Shutdown", ctx)} +} + +func (_c *MockSingleTaskPoller_Shutdown_Call) Run(run func(ctx context.Context)) *MockSingleTaskPoller_Shutdown_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockSingleTaskPoller_Shutdown_Call) Return(err error) *MockSingleTaskPoller_Shutdown_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockSingleTaskPoller_Shutdown_Call) RunAndReturn(run func(ctx context.Context) error) *MockSingleTaskPoller_Shutdown_Call { + _c.Call.Return(run) + return _c +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.8.1/internal/app/poll/single.go new/forgejo-runner-12.8.2/internal/app/poll/single.go --- old/forgejo-runner-12.8.1/internal/app/poll/single.go 2026-04-06 19:20:18.000000000 +0200 +++ new/forgejo-runner-12.8.2/internal/app/poll/single.go 2026-04-07 17:51:41.000000000 +0200 @@ -17,6 +17,17 @@ "golang.org/x/time/rate" ) +// ErrNoTaskReceived signals that no task was received from the server. +var ErrNoTaskReceived = errors.New("no task received") + +//mockery:generate: true +//mockery:filename: mocks/single.go +//mockery:pkgname: mocks +type SingleTaskPoller interface { + Poll() error + Shutdown(ctx context.Context) error +} + type singleTaskPoller struct { client client.Client runner run.RunnerInterface @@ -34,7 +45,7 @@ handle *string } -func NewSingleTaskPoller(ctx context.Context, cfg *config.Config, client client.Client, runner run.RunnerInterface, wait bool, handle *string) Poller { +func NewSingleTaskPoller(ctx context.Context, cfg *config.Config, client client.Client, runner run.RunnerInterface, wait bool, handle *string) SingleTaskPoller { pollingCtx, cancelPolling := context.WithCancel(ctx) taskCtx, cancelTasks := context.WithCancel(ctx) @@ -52,7 +63,7 @@ } } -func (s *singleTaskPoller) Poll() { +func (s *singleTaskPoller) Poll() error { rateLimiter := rate.NewLimiter(rate.Every(s.client.FetchInterval()), 1) log.Info("single task poller launched") @@ -77,7 +88,7 @@ if err := rateLimiter.Wait(s.pollingCtx); err != nil { log.Infof("single task poller is shutting down") close(s.done) - return + return nil } log.Tracef("single task poller asking client %s for a task", s.client.Address()) @@ -100,11 +111,13 @@ continue } + var err error if task != nil { log.Infof("single task poller successfully fetched one task from %s", s.client.Address()) s.runner.Run(s.taskCtx, task) } else { - log.Infof("single task poller received no task from %s", s.client.Address()) + log.Debugf("single task poller received no task from %s", s.client.Address()) + err = ErrNoTaskReceived } log.Info("single task poller is shutting down") @@ -113,7 +126,7 @@ // Signal that the poller is done. close(s.done) - return + return err } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.8.1/internal/app/poll/single_test.go new/forgejo-runner-12.8.2/internal/app/poll/single_test.go --- old/forgejo-runner-12.8.1/internal/app/poll/single_test.go 2026-04-06 19:20:18.000000000 +0200 +++ new/forgejo-runner-12.8.2/internal/app/poll/single_test.go 2026-04-07 17:51:41.000000000 +0200 @@ -13,6 +13,7 @@ "code.forgejo.org/forgejo/runner/v12/internal/pkg/config" "connectrpc.com/connect" "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" ) func TestSingleTaskPoller_FetchesAvailableTask(t *testing.T) { @@ -32,9 +33,26 @@ Run(func(args mock.Arguments) {}) taskPoller := NewSingleTaskPoller(t.Context(), cfg, mockClient, mockRunner, false, nil) - taskPoller.Poll() + err := taskPoller.Poll() + require.NoError(t, err) +} + +func TestSingleTaskPoller_ReturnsErrorWhenNoTaskReceived(t *testing.T) { + cfg := &config.Config{} - mockRunner.On("Run", mock.Anything, mock.Anything) + mockClient := mock_client.NewMockClient(t) + mockClient. + On("Address").Return("https://example.com/forgejo"). + On("SetRequestKey", mock.Anything).Return(func() {}). + On("FetchInterval").Return(time.Millisecond). + On("FetchTask", mock.Anything, connect.NewRequest(&runnerv1.FetchTaskRequest{})). + Return(connect.NewResponse(&runnerv1.FetchTaskResponse{Task: nil, TasksVersion: int64(1)}), nil) + + mockRunner := mock_runner.NewMockRunner(t) + + taskPoller := NewSingleTaskPoller(t.Context(), cfg, mockClient, mockRunner, false, nil) + err := taskPoller.Poll() + require.ErrorIs(t, err, ErrNoTaskReceived) } func TestSingleTaskPoller_WaitsForAvailableTask(t *testing.T) { @@ -56,9 +74,8 @@ Run(func(args mock.Arguments) {}) taskPoller := NewSingleTaskPoller(t.Context(), cfg, mockClient, mockRunner, true, nil) - taskPoller.Poll() - - mockRunner.On("Run", mock.Anything, mock.Anything) + err := taskPoller.Poll() + require.NoError(t, err) } func TestSingleTaskPoller_FetchesRequestedTask(t *testing.T) { @@ -80,9 +97,28 @@ Run(func(args mock.Arguments) {}) taskPoller := NewSingleTaskPoller(t.Context(), cfg, mockClient, mockRunner, false, &handle) - taskPoller.Poll() + err := taskPoller.Poll() + require.NoError(t, err) +} - mockRunner.On("Run", mock.Anything, mock.Anything) +func TestSingleTaskPoller_ReturnsErrorWhenRequestedTaskNotReceived(t *testing.T) { + cfg := &config.Config{} + + handle := "fc0dbe3b-aca2-4ad7-9b7e-d8d7a15dfc42" + + mockClient := mock_client.NewMockClient(t) + mockClient. + On("Address").Return("https://example.com/forgejo"). + On("SetRequestKey", mock.Anything).Return(func() {}). + On("FetchInterval").Return(time.Millisecond). + On("FetchSingleTask", mock.Anything, connect.NewRequest(&runnerv1.FetchSingleTaskRequest{Handle: &handle})). + Return(connect.NewResponse(&runnerv1.FetchSingleTaskResponse{Task: nil, TasksVersion: int64(1)}), nil) + + mockRunner := mock_runner.NewMockRunner(t) + + taskPoller := NewSingleTaskPoller(t.Context(), cfg, mockClient, mockRunner, false, &handle) + err := taskPoller.Poll() + require.ErrorIs(t, err, ErrNoTaskReceived) } func TestSingleTaskPoller_WaitsForRequestedTask(t *testing.T) { @@ -106,7 +142,6 @@ Run(func(args mock.Arguments) {}) taskPoller := NewSingleTaskPoller(t.Context(), cfg, mockClient, mockRunner, true, &handle) - taskPoller.Poll() - - mockRunner.On("Run", mock.Anything, mock.Anything) + err := taskPoller.Poll() + require.NoError(t, err) } ++++++ forgejo-runner.obsinfo ++++++ --- /var/tmp/diff_new_pack.0sIe14/_old 2026-04-10 18:02:07.116397615 +0200 +++ /var/tmp/diff_new_pack.0sIe14/_new 2026-04-10 18:02:07.124397945 +0200 @@ -1,5 +1,5 @@ name: forgejo-runner -version: 12.8.1 -mtime: 1775496018 -commit: 9eb2ed865c2bef5f5c8a38191bde86b3645442dc +version: 12.8.2 +mtime: 1775577101 +commit: 483dc418ecb029b3c0f05faefebac547847aa950 ++++++ vendor.tar.gz ++++++
