1// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2// See LICENSE.txt for license information. 3 4package app 5 6import ( 7 "encoding/json" 8 "testing" 9 "time" 10 11 "github.com/stretchr/testify/require" 12 13 "github.com/mattermost/mattermost-server/v6/einterfaces" 14 "github.com/mattermost/mattermost-server/v6/model" 15) 16 17func TestBusySet(t *testing.T) { 18 cluster := &ClusterMock{Busy: &Busy{}} 19 busy := NewBusy(cluster) 20 21 isNotBusy := func() bool { 22 return !busy.IsBusy() 23 } 24 25 require.False(t, busy.IsBusy()) 26 27 busy.Set(time.Millisecond * 100) 28 require.True(t, busy.IsBusy()) 29 require.True(t, compareBusyState(t, busy, cluster.Busy)) 30 // should automatically expire after 100ms. 31 require.Eventually(t, isNotBusy, time.Second*15, time.Millisecond*20) 32 // allow a moment for cluster to sync. 33 require.Eventually(t, func() bool { return compareBusyState(t, busy, cluster.Busy) }, time.Second*15, time.Millisecond*20) 34 35 // test set after auto expiry. 36 busy.Set(time.Second * 30) 37 require.True(t, busy.IsBusy()) 38 require.True(t, compareBusyState(t, busy, cluster.Busy)) 39 expire := busy.Expires() 40 require.Greater(t, expire.Unix(), time.Now().Add(time.Second*10).Unix()) 41 42 // test extending existing expiry 43 busy.Set(time.Minute * 5) 44 require.True(t, busy.IsBusy()) 45 require.True(t, compareBusyState(t, busy, cluster.Busy)) 46 expire = busy.Expires() 47 require.Greater(t, expire.Unix(), time.Now().Add(time.Minute*2).Unix()) 48 49 busy.Clear() 50 require.False(t, busy.IsBusy()) 51 require.True(t, compareBusyState(t, busy, cluster.Busy)) 52} 53 54func TestBusyExpires(t *testing.T) { 55 cluster := &ClusterMock{Busy: &Busy{}} 56 busy := NewBusy(cluster) 57 58 isNotBusy := func() bool { 59 return !busy.IsBusy() 60 } 61 62 // get expiry before it is set 63 expire := busy.Expires() 64 // should be time.Time zero value 65 require.Equal(t, time.Time{}.Unix(), expire.Unix()) 66 67 // get expiry after it is set 68 busy.Set(time.Minute * 5) 69 expire = busy.Expires() 70 require.Greater(t, expire.Unix(), time.Now().Add(time.Minute*2).Unix()) 71 require.True(t, compareBusyState(t, busy, cluster.Busy)) 72 73 // get expiry after clear 74 busy.Clear() 75 expire = busy.Expires() 76 // should be time.Time zero value 77 require.Equal(t, time.Time{}.Unix(), expire.Unix()) 78 require.True(t, compareBusyState(t, busy, cluster.Busy)) 79 80 // get expiry after auto-expire 81 busy.Set(time.Millisecond * 100) 82 require.Eventually(t, isNotBusy, time.Second*5, time.Millisecond*20) 83 expire = busy.Expires() 84 // should be time.Time zero value 85 require.Equal(t, time.Time{}.Unix(), expire.Unix()) 86 // allow a moment for cluster to sync 87 require.Eventually(t, func() bool { return compareBusyState(t, busy, cluster.Busy) }, time.Second*15, time.Millisecond*20) 88} 89 90func TestBusyRace(t *testing.T) { 91 cluster := &ClusterMock{Busy: &Busy{}} 92 busy := NewBusy(cluster) 93 94 busy.Set(500 * time.Millisecond) 95 96 // We are sleeping in order to let the race trigger. 97 time.Sleep(time.Second) 98} 99 100func compareBusyState(t *testing.T, busy1 *Busy, busy2 *Busy) bool { 101 t.Helper() 102 if busy1.IsBusy() != busy2.IsBusy() { 103 busy1JSON, _ := busy1.ToJSON() 104 busy2JSON, _ := busy2.ToJSON() 105 t.Logf("busy1:%s; busy2:%s\n", busy1JSON, busy2JSON) 106 return false 107 } 108 if busy1.Expires().Unix() != busy2.Expires().Unix() { 109 busy1JSON, _ := busy1.ToJSON() 110 busy2JSON, _ := busy2.ToJSON() 111 t.Logf("busy1:%s; busy2:%s\n", busy1JSON, busy2JSON) 112 return false 113 } 114 return true 115} 116 117// ClusterMock simulates the busy state of a cluster. 118type ClusterMock struct { 119 Busy *Busy 120} 121 122func (c *ClusterMock) SendClusterMessage(msg *model.ClusterMessage) { 123 var sbs model.ServerBusyState 124 json.Unmarshal(msg.Data, &sbs) 125 c.Busy.ClusterEventChanged(&sbs) 126} 127 128func (c *ClusterMock) SendClusterMessageToNode(nodeID string, msg *model.ClusterMessage) error { 129 return nil 130} 131 132func (c *ClusterMock) StartInterNodeCommunication() {} 133func (c *ClusterMock) StopInterNodeCommunication() {} 134func (c *ClusterMock) RegisterClusterMessageHandler(event model.ClusterEvent, crm einterfaces.ClusterMessageHandler) { 135} 136func (c *ClusterMock) GetClusterId() string { return "cluster_mock" } 137func (c *ClusterMock) IsLeader() bool { return false } 138func (c *ClusterMock) GetMyClusterInfo() *model.ClusterInfo { return nil } 139func (c *ClusterMock) GetClusterInfos() []*model.ClusterInfo { return nil } 140func (c *ClusterMock) NotifyMsg(buf []byte) {} 141func (c *ClusterMock) GetClusterStats() ([]*model.ClusterStats, *model.AppError) { return nil, nil } 142func (c *ClusterMock) GetLogs(page, perPage int) ([]string, *model.AppError) { return nil, nil } 143func (c *ClusterMock) GetPluginStatuses() (model.PluginStatuses, *model.AppError) { return nil, nil } 144func (c *ClusterMock) ConfigChanged(previousConfig *model.Config, newConfig *model.Config, sendToOtherServer bool) *model.AppError { 145 return nil 146} 147func (c *ClusterMock) HealthScore() int { return 0 } 148