// Copyright 2026 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: GPL-3.0-or-later package integration import ( "errors" "sync" "testing" "time" actions_model "forgejo.org/models/actions" "forgejo.org/models/unittest" "forgejo.org/modules/setting" "forgejo.org/tests" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestCreateTaskForRunnerNoMatchingJobFound(t *testing.T) { defer unittest.OverrideFixtures("tests/integration/fixtures/TestCreateTaskForRunner")() defer tests.PrepareTestEnv(t)() runner := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{ID: 1004}) _, err := actions_model.CreateTaskForRunner(t.Context(), runner, nil, nil) require.ErrorIs(t, err, actions_model.ErrNoMatchingJobFound) } // We need to concurrently choose the same job with two requests to CreateTaskForRunner. The second // request that tries to update the job in the database (that was already updated by the first // request) then chokes and returns the error ErrNoJobUpdated. func TestCreateTaskForRunnerNoJobUpdated(t *testing.T) { if setting.Database.Type.IsSQLite3() { // SQLite locks on the transaction and the desired race condition can't be achieved t.Skip() } defer unittest.OverrideFixtures("tests/integration/fixtures/TestCreateTaskForRunner")() defer tests.PrepareTestEnv(t)() assert.Eventually( t, func() bool { unittest.LoadFixtures() runner := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{ID: 1005}) var w sync.WaitGroup errs := make(chan error, 2) for range 2 { w.Go(func() { _, err := actions_model.CreateTaskForRunner(t.Context(), runner, nil, nil) errs <- err }) } w.Wait() close(errs) gotNoJobUpdated := false succeded := false for err := range errs { if errors.Is(err, actions_model.ErrNoJobUpdated) { gotNoJobUpdated = true } if err == nil { succeded = true } } return succeded && gotNoJobUpdated }, 30*time.Second, 500*time.Microsecond, "one call to CreateTaskForRunner should succeed and the other one should return the error ErrNoJobUpdated", ) }