snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
This commit is contained in:
408
server/public/pluginapi/cluster/job_test.go
Normal file
408
server/public/pluginapi/cluster/job_test.go
Normal file
@@ -0,0 +1,408 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMakeWaitForInterval(t *testing.T) {
|
||||
t.Run("panics on invalid interval", func(t *testing.T) {
|
||||
assert.Panics(t, func() {
|
||||
MakeWaitForInterval(0)
|
||||
})
|
||||
})
|
||||
|
||||
const neverRun = -1 * time.Second
|
||||
|
||||
testCases := []struct {
|
||||
Description string
|
||||
Interval time.Duration
|
||||
LastFinished time.Duration
|
||||
Expected time.Duration
|
||||
}{
|
||||
{
|
||||
"never run, 5 minutes",
|
||||
5 * time.Minute,
|
||||
neverRun,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"run 1 minute ago, 5 minutes",
|
||||
5 * time.Minute,
|
||||
-1 * time.Minute,
|
||||
4 * time.Minute,
|
||||
},
|
||||
{
|
||||
"run 2 minutes ago, 5 minutes",
|
||||
5 * time.Minute,
|
||||
-2 * time.Minute,
|
||||
3 * time.Minute,
|
||||
},
|
||||
{
|
||||
"run 4 minutes 30 seconds ago, 5 minutes",
|
||||
5 * time.Minute,
|
||||
-4*time.Minute - 30*time.Second,
|
||||
30 * time.Second,
|
||||
},
|
||||
{
|
||||
"run 4 minutes 59 seconds ago, 5 minutes",
|
||||
5 * time.Minute,
|
||||
-4*time.Minute - 59*time.Second,
|
||||
1 * time.Second,
|
||||
},
|
||||
{
|
||||
"never run, 1 hour",
|
||||
1 * time.Hour,
|
||||
neverRun,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"run 1 minute ago, 1 hour",
|
||||
1 * time.Hour,
|
||||
-1 * time.Minute,
|
||||
59 * time.Minute,
|
||||
},
|
||||
{
|
||||
"run 20 minutes ago, 1 hour",
|
||||
1 * time.Hour,
|
||||
-20 * time.Minute,
|
||||
40 * time.Minute,
|
||||
},
|
||||
{
|
||||
"run 55 minutes 30 seconds ago, 1 hour",
|
||||
1 * time.Hour,
|
||||
-55*time.Minute - 30*time.Second,
|
||||
4*time.Minute + 30*time.Second,
|
||||
},
|
||||
{
|
||||
"run 59 minutes 59 seconds ago, 1 hour",
|
||||
1 * time.Hour,
|
||||
-59*time.Minute - 59*time.Second,
|
||||
1 * time.Second,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.Description, func(t *testing.T) {
|
||||
now := time.Now()
|
||||
|
||||
var lastFinished time.Time
|
||||
if testCase.LastFinished != neverRun {
|
||||
lastFinished = now.Add(testCase.LastFinished)
|
||||
}
|
||||
|
||||
actual := MakeWaitForInterval(testCase.Interval)(now, JobMetadata{
|
||||
LastFinished: lastFinished,
|
||||
})
|
||||
assert.Equal(t, testCase.Expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeWaitForRoundedInterval(t *testing.T) {
|
||||
t.Run("panics on invalid interval", func(t *testing.T) {
|
||||
assert.Panics(t, func() {
|
||||
MakeWaitForRoundedInterval(0)
|
||||
})
|
||||
})
|
||||
|
||||
const neverRun = -1 * time.Second
|
||||
topOfTheHour := time.Now().Truncate(1 * time.Hour)
|
||||
topOfTheDay := time.Now().Truncate(24 * time.Hour)
|
||||
|
||||
testCases := []struct {
|
||||
Description string
|
||||
Interval time.Duration
|
||||
Now time.Time
|
||||
LastFinished time.Duration
|
||||
Expected time.Duration
|
||||
}{
|
||||
{
|
||||
"5 minutes, top of the hour, never run",
|
||||
5 * time.Minute,
|
||||
topOfTheHour,
|
||||
neverRun,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"5 minutes, top of the hour less 1 minute, never run",
|
||||
5 * time.Minute,
|
||||
topOfTheHour.Add(-1 * time.Minute),
|
||||
neverRun,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"5 minutes, top of the hour less 1 minute, run 1 minute ago",
|
||||
5 * time.Minute,
|
||||
topOfTheHour.Add(-1 * time.Minute),
|
||||
-1 * time.Minute,
|
||||
1 * time.Minute,
|
||||
},
|
||||
{
|
||||
"5 minutes, top of the hour plus 1 minute, run 2 minutes ago",
|
||||
5 * time.Minute,
|
||||
topOfTheHour.Add(1 * time.Minute),
|
||||
-2 * time.Minute,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"5 minutes, top of the hour plus 1 minute, run 30 seconds ago",
|
||||
5 * time.Minute,
|
||||
topOfTheHour.Add(1 * time.Minute),
|
||||
-30 * time.Second,
|
||||
4 * time.Minute,
|
||||
},
|
||||
{
|
||||
"5 minutes, top of the hour plus 7 minutes, run 30 seconds ago",
|
||||
5 * time.Minute,
|
||||
topOfTheHour.Add(7 * time.Minute),
|
||||
-30 * time.Second,
|
||||
3 * time.Minute,
|
||||
},
|
||||
{
|
||||
"30 minutes, top of the hour, never run",
|
||||
30 * time.Minute,
|
||||
topOfTheHour,
|
||||
neverRun,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"30 minutes, top of the hour less 1 minute, never run",
|
||||
30 * time.Minute,
|
||||
topOfTheHour.Add(-1 * time.Minute),
|
||||
neverRun,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"30 minutes, top of the hour less 1 minute, run 1 minute ago",
|
||||
30 * time.Minute,
|
||||
topOfTheHour.Add(-1 * time.Minute),
|
||||
-1 * time.Minute,
|
||||
1 * time.Minute,
|
||||
},
|
||||
{
|
||||
"30 minutes, top of the hour plus 1 minute, run 2 minutes ago",
|
||||
30 * time.Minute,
|
||||
topOfTheHour.Add(1 * time.Minute),
|
||||
-2 * time.Minute,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"30 minutes, top of the hour plus 1 minute, run 30 seconds ago",
|
||||
30 * time.Minute,
|
||||
topOfTheHour.Add(1 * time.Minute),
|
||||
-30 * time.Second,
|
||||
29 * time.Minute,
|
||||
},
|
||||
{
|
||||
"30 minutes, top of the hour plus 7 minutes, run 30 seconds ago",
|
||||
30 * time.Minute,
|
||||
topOfTheHour.Add(7 * time.Minute),
|
||||
-30 * time.Second,
|
||||
23 * time.Minute,
|
||||
},
|
||||
{
|
||||
"24 hours, top of the day, never run",
|
||||
24 * time.Hour,
|
||||
topOfTheDay,
|
||||
neverRun,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"24 hours, top of the day less 1 minute, never run",
|
||||
24 * time.Hour,
|
||||
topOfTheDay.Add(-1 * time.Minute),
|
||||
neverRun,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"24 hours, top of the day less 1 minute, run 1 minute ago",
|
||||
24 * time.Hour,
|
||||
topOfTheDay.Add(-1 * time.Minute),
|
||||
-1 * time.Minute,
|
||||
1 * time.Minute,
|
||||
},
|
||||
{
|
||||
"24 hours, top of the day plus 1 minute, run 2 minutes ago",
|
||||
24 * time.Hour,
|
||||
topOfTheDay.Add(1 * time.Minute),
|
||||
-2 * time.Minute,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"24 hours, top of the day plus 1 minute, run 30 seconds ago",
|
||||
24 * time.Hour,
|
||||
topOfTheDay.Add(1 * time.Minute),
|
||||
-30 * time.Second,
|
||||
23*time.Hour + 59*time.Minute,
|
||||
},
|
||||
{
|
||||
"24 hours, top of the day plus 7 minutes, run 30 seconds ago",
|
||||
24 * time.Hour,
|
||||
topOfTheDay.Add(7 * time.Minute),
|
||||
-30 * time.Second,
|
||||
23*time.Hour + 53*time.Minute,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.Description, func(t *testing.T) {
|
||||
var lastFinished time.Time
|
||||
if testCase.LastFinished != neverRun {
|
||||
lastFinished = testCase.Now.Add(testCase.LastFinished)
|
||||
}
|
||||
|
||||
actual := MakeWaitForRoundedInterval(testCase.Interval)(testCase.Now, JobMetadata{
|
||||
LastFinished: lastFinished,
|
||||
})
|
||||
assert.Equal(t, testCase.Expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSchedule(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
makeKey := model.NewId
|
||||
|
||||
t.Run("single-threaded", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mockPluginAPI := newMockPluginAPI(t)
|
||||
|
||||
count := new(int32)
|
||||
callback := func() {
|
||||
atomic.AddInt32(count, 1)
|
||||
}
|
||||
|
||||
job, err := Schedule(mockPluginAPI, makeKey(), MakeWaitForInterval(100*time.Millisecond), callback)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, job)
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
err = job.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// Shouldn't have hit 20 in this time frame
|
||||
assert.Less(t, *count, int32(20))
|
||||
|
||||
// Should have hit at least 5 in this time frame
|
||||
assert.Greater(t, *count, int32(5))
|
||||
})
|
||||
|
||||
t.Run("multi-threaded, single job", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mockPluginAPI := newMockPluginAPI(t)
|
||||
|
||||
count := new(int32)
|
||||
callback := func() {
|
||||
atomic.AddInt32(count, 1)
|
||||
}
|
||||
|
||||
var jobs []*Job
|
||||
|
||||
key := makeKey()
|
||||
|
||||
for range 3 {
|
||||
job, err := Schedule(mockPluginAPI, key, MakeWaitForInterval(100*time.Millisecond), callback)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, job)
|
||||
|
||||
jobs = append(jobs, job)
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := range 3 {
|
||||
job := jobs[i]
|
||||
wg.Go(func() {
|
||||
err := job.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// Shouldn't have hit 20 in this time frame
|
||||
assert.Less(t, *count, int32(20))
|
||||
|
||||
// Should have hit at least 5 in this time frame
|
||||
assert.Greater(t, *count, int32(5))
|
||||
})
|
||||
|
||||
t.Run("multi-threaded, multiple jobs", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mockPluginAPI := newMockPluginAPI(t)
|
||||
|
||||
countA := new(int32)
|
||||
callbackA := func() {
|
||||
atomic.AddInt32(countA, 1)
|
||||
}
|
||||
|
||||
countB := new(int32)
|
||||
callbackB := func() {
|
||||
atomic.AddInt32(countB, 1)
|
||||
}
|
||||
|
||||
keyA := makeKey()
|
||||
keyB := makeKey()
|
||||
|
||||
var jobs []*Job
|
||||
for i := range 3 {
|
||||
var key string
|
||||
var callback func()
|
||||
if i <= 1 {
|
||||
key = keyA
|
||||
callback = callbackA
|
||||
} else {
|
||||
key = keyB
|
||||
callback = callbackB
|
||||
}
|
||||
|
||||
job, err := Schedule(mockPluginAPI, key, MakeWaitForInterval(100*time.Millisecond), callback)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, job)
|
||||
|
||||
jobs = append(jobs, job)
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := range 3 {
|
||||
job := jobs[i]
|
||||
wg.Go(func() {
|
||||
err := job.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// Shouldn't have hit 20 in this time frame
|
||||
assert.Less(t, *countA, int32(20))
|
||||
|
||||
// Should have hit at least 5 in this time frame
|
||||
assert.Greater(t, *countA, int32(5))
|
||||
|
||||
// Shouldn't have hit 20 in this time frame
|
||||
assert.Less(t, *countB, int32(20))
|
||||
|
||||
// Should have hit at least 5 in this time frame
|
||||
assert.Greater(t, *countB, int32(5))
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user