github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/chat/convloader_test.go (about)

     1  package chat
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/keybase/client/go/chat/types"
     9  	"github.com/keybase/client/go/kbtest"
    10  	"github.com/keybase/client/go/protocol/chat1"
    11  	"github.com/keybase/client/go/protocol/gregor1"
    12  	"github.com/keybase/client/go/protocol/keybase1"
    13  	"github.com/keybase/clockwork"
    14  	"github.com/stretchr/testify/require"
    15  )
    16  
    17  func setupLoaderTest(t *testing.T) (context.Context, *kbtest.ChatTestContext, *kbtest.ChatMockWorld,
    18  	func() chat1.RemoteInterface, types.Sender, *chatListener, chat1.NewConversationRemoteRes) {
    19  	ctx, world, ri, _, baseSender, listener := setupTest(t, 1)
    20  
    21  	u := world.GetUsers()[0]
    22  	tc := world.Tcs[u.Username]
    23  	trip := newConvTriple(ctx, t, tc, u.Username)
    24  	firstMessagePlaintext := chat1.MessagePlaintext{
    25  		ClientHeader: chat1.MessageClientHeader{
    26  			Conv:        trip,
    27  			TlfName:     u.Username,
    28  			TlfPublic:   false,
    29  			MessageType: chat1.MessageType_TLFNAME,
    30  		},
    31  		MessageBody: chat1.MessageBody{},
    32  	}
    33  	prepareRes, err := baseSender.Prepare(ctx, firstMessagePlaintext,
    34  		chat1.ConversationMembersType_IMPTEAMNATIVE, nil, nil)
    35  	firstMessageBoxed := prepareRes.Boxed
    36  	require.NoError(t, err)
    37  	res, err := ri.NewConversationRemote2(ctx, chat1.NewConversationRemote2Arg{
    38  		IdTriple:   trip,
    39  		TLFMessage: firstMessageBoxed,
    40  	})
    41  	require.NoError(t, err)
    42  	return ctx, tc, world, func() chat1.RemoteInterface { return ri }, baseSender, listener, res
    43  }
    44  
    45  func TestConvLoader(t *testing.T) {
    46  	ctx, tc, world, _, _, listener, res := setupLoaderTest(t)
    47  	defer world.Cleanup()
    48  
    49  	require.NoError(t, tc.Context().ConvLoader.Queue(ctx,
    50  		types.NewConvLoaderJob(res.ConvID, nil, types.ConvLoaderPriorityHigh, types.ConvLoaderUnique, nil)))
    51  	select {
    52  	case convID := <-listener.bgConvLoads:
    53  		if !convID.Eq(res.ConvID) {
    54  			t.Errorf("loaded conv id: %s, expected %s", convID, res.ConvID)
    55  		}
    56  	case <-time.After(20 * time.Second):
    57  		t.Fatal("timeout waiting for conversation load")
    58  	}
    59  }
    60  
    61  type slowestRemote struct {
    62  	chat1.RemoteInterface
    63  	callCh chan struct{}
    64  }
    65  
    66  func makeSlowestRemote() slowestRemote {
    67  	return slowestRemote{
    68  		callCh: make(chan struct{}, 5),
    69  	}
    70  }
    71  
    72  func (s slowestRemote) delay(ctx context.Context) {
    73  	s.callCh <- struct{}{}
    74  	select {
    75  	case <-ctx.Done():
    76  	case <-time.After(24 * time.Hour):
    77  	}
    78  }
    79  
    80  func (s slowestRemote) GetThreadRemote(ctx context.Context, arg chat1.GetThreadRemoteArg) (res chat1.GetThreadRemoteRes, err error) {
    81  	s.delay(ctx)
    82  	return res, context.Canceled
    83  }
    84  
    85  func (s slowestRemote) GetMessagesRemote(ctx context.Context, arg chat1.GetMessagesRemoteArg) (res chat1.GetMessagesRemoteRes, err error) {
    86  	s.delay(ctx)
    87  	return res, context.Canceled
    88  }
    89  
    90  func TestConvLoaderSuspend(t *testing.T) {
    91  	_, tc, world, _, _, listener, res := setupLoaderTest(t)
    92  	defer world.Cleanup()
    93  
    94  	ri := tc.ChatG.ConvSource.(*HybridConversationSource).ri
    95  	slowRi := makeSlowestRemote()
    96  	tc.ChatG.ConvSource.(*HybridConversationSource).ri = func() chat1.RemoteInterface {
    97  		return slowRi
    98  	}
    99  	require.NoError(t, tc.Context().ConvLoader.Queue(context.TODO(),
   100  		types.NewConvLoaderJob(res.ConvID, nil, types.ConvLoaderPriorityHigh, types.ConvLoaderUnique, nil)))
   101  	select {
   102  	case <-slowRi.callCh:
   103  	case <-time.After(20 * time.Second):
   104  		require.Fail(t, "no remote call")
   105  	}
   106  	require.True(t, tc.Context().ConvLoader.Suspend(context.TODO()))
   107  	select {
   108  	case <-listener.bgConvLoads:
   109  		require.Fail(t, "no load yet")
   110  	default:
   111  	}
   112  	require.False(t, tc.Context().ConvLoader.Suspend(context.TODO()))
   113  
   114  	tc.ChatG.ConvSource.(*HybridConversationSource).ri = ri
   115  	require.False(t, tc.Context().ConvLoader.Resume(context.TODO()))
   116  	select {
   117  	case <-listener.bgConvLoads:
   118  		require.Fail(t, "no load yet")
   119  	default:
   120  	}
   121  	require.True(t, tc.Context().ConvLoader.Resume(context.TODO()))
   122  	select {
   123  	case convID := <-listener.bgConvLoads:
   124  		require.Equal(t, res.ConvID, convID)
   125  	case <-time.After(20 * time.Second):
   126  		require.Fail(t, "no event")
   127  	}
   128  }
   129  
   130  func TestConvLoaderAppState(t *testing.T) {
   131  	_, tc, world, _, _, listener, res := setupLoaderTest(t)
   132  	defer world.Cleanup()
   133  
   134  	clock := clockwork.NewFakeClock()
   135  	appStateCh := make(chan struct{})
   136  	tc.ChatG.ConvLoader.(*BackgroundConvLoader).loadWait = 0
   137  	tc.ChatG.ConvLoader.(*BackgroundConvLoader).clock = clock
   138  	tc.ChatG.ConvLoader.(*BackgroundConvLoader).appStateCh = appStateCh
   139  	ri := tc.ChatG.ConvSource.(*HybridConversationSource).ri
   140  	_ = ri
   141  	slowRi := makeSlowestRemote()
   142  	failDuration := 2 * time.Second
   143  	uid := gregor1.UID(tc.G.Env.GetUID().ToBytes())
   144  	// Test that a foreground with no background doesnt do anything
   145  	tc.ChatG.ConvSource.(*HybridConversationSource).ri = func() chat1.RemoteInterface {
   146  		return slowRi
   147  	}
   148  	_ = tc.ChatG.ConvSource.(*HybridConversationSource).Clear(context.TODO(), res.ConvID, uid, nil)
   149  	require.NoError(t, tc.Context().ConvLoader.Queue(context.TODO(),
   150  		types.NewConvLoaderJob(res.ConvID, nil, types.ConvLoaderPriorityHigh, types.ConvLoaderUnique, nil)))
   151  	clock.BlockUntil(1)
   152  	clock.Advance(200 * time.Millisecond) // Get by small sleep
   153  	select {
   154  	case <-slowRi.callCh:
   155  	case <-time.After(failDuration):
   156  		require.Fail(t, "no remote call")
   157  	}
   158  	require.True(t, tc.Context().ConvLoader.Suspend(context.TODO()))
   159  	tc.G.MobileAppState.Update(keybase1.MobileAppState_FOREGROUND)
   160  	select {
   161  	case <-appStateCh:
   162  		require.Fail(t, "no app state")
   163  	default:
   164  	}
   165  	select {
   166  	case <-listener.bgConvLoads:
   167  		require.Fail(t, "no load yet")
   168  	default:
   169  	}
   170  	tc.ChatG.ConvSource.(*HybridConversationSource).ri = ri
   171  	require.True(t, tc.Context().ConvLoader.Resume(context.TODO()))
   172  	clock.BlockUntil(1)
   173  	clock.Advance(2 * time.Second) // Get by resume wait
   174  	clock.BlockUntil(1)
   175  	clock.Advance(time.Hour) // Get by small sleep
   176  	select {
   177  	case convID := <-listener.bgConvLoads:
   178  		require.Equal(t, res.ConvID, convID)
   179  	case <-time.After(failDuration):
   180  		require.Fail(t, "no event")
   181  	}
   182  	t.Logf("testing foreground/background")
   183  	// Test that background/foreground works
   184  	_ = tc.ChatG.ConvSource.(*HybridConversationSource).Clear(context.TODO(), res.ConvID, uid, nil)
   185  	tc.ChatG.ConvSource.(*HybridConversationSource).ri = func() chat1.RemoteInterface {
   186  		return slowRi
   187  	}
   188  
   189  	require.NoError(t, tc.Context().ConvLoader.Queue(context.TODO(),
   190  		types.NewConvLoaderJob(res.ConvID, nil, types.ConvLoaderPriorityHigh, types.ConvLoaderUnique, nil)))
   191  
   192  	clock.BlockUntil(1)
   193  	clock.Advance(200 * time.Millisecond) // Get by small sleep
   194  	select {
   195  	case <-slowRi.callCh:
   196  	case <-time.After(failDuration):
   197  		require.Fail(t, "no remote call")
   198  	}
   199  	tc.G.MobileAppState.Update(keybase1.MobileAppState_BACKGROUND)
   200  	select {
   201  	case <-appStateCh:
   202  	case <-time.After(failDuration):
   203  		require.Fail(t, "no app state")
   204  	}
   205  	tc.ChatG.ConvSource.(*HybridConversationSource).ri = ri
   206  	tc.G.MobileAppState.Update(keybase1.MobileAppState_FOREGROUND)
   207  	select {
   208  	case <-appStateCh:
   209  	case <-time.After(failDuration):
   210  		require.Fail(t, "no app state")
   211  	}
   212  	// Need to advance clock
   213  	select {
   214  	case <-listener.bgConvLoads:
   215  		require.Fail(t, "no load yet")
   216  	default:
   217  	}
   218  
   219  	clock.BlockUntil(1)
   220  	clock.Advance(10 * time.Second)
   221  	clock.BlockUntil(1)
   222  	clock.Advance(time.Hour) // Get by small sleep
   223  	select {
   224  	case convID := <-listener.bgConvLoads:
   225  		require.Equal(t, res.ConvID, convID)
   226  	case <-time.After(failDuration):
   227  		require.Fail(t, "no event")
   228  	}
   229  }
   230  
   231  func TestConvLoaderPageBack(t *testing.T) {
   232  	ctx, tc, world, ri, sender, listener, res := setupLoaderTest(t)
   233  	defer world.Cleanup()
   234  
   235  	ib, err := ri().GetInboxRemote(ctx, chat1.GetInboxRemoteArg{
   236  		Query: &chat1.GetInboxQuery{
   237  			ConvID: &res.ConvID,
   238  		},
   239  	})
   240  	require.NoError(t, err)
   241  	require.Equal(t, 1, len(ib.Inbox.Full().Conversations))
   242  	conv := ib.Inbox.Full().Conversations[0]
   243  
   244  	u := world.GetUsers()[0]
   245  	skp, err := sender.(*BlockingSender).getSigningKeyPair(ctx)
   246  	require.NoError(t, err)
   247  	for i := 0; i < 2; i++ {
   248  		pt := chat1.MessagePlaintext{
   249  			ClientHeader: chat1.MessageClientHeader{
   250  				Conv:        conv.Metadata.IdTriple,
   251  				Sender:      u.User.GetUID().ToBytes(),
   252  				TlfName:     u.Username,
   253  				MessageType: chat1.MessageType_TEXT,
   254  			},
   255  			MessageBody: chat1.NewMessageBodyWithText(chat1.MessageText{Body: "foo"}),
   256  		}
   257  		boxed, err := NewBoxer(tc.Context()).BoxMessage(ctx, pt, conv.GetMembersType(), skp, nil)
   258  		require.NoError(t, err)
   259  		_, err = ri().PostRemote(ctx, chat1.PostRemoteArg{
   260  			ConversationID: conv.GetConvID(),
   261  			MessageBoxed:   boxed,
   262  		})
   263  		require.NoError(t, err)
   264  	}
   265  	select {
   266  	case <-listener.bgConvLoads:
   267  		require.Fail(t, "no loads here")
   268  	default:
   269  	}
   270  
   271  	require.NoError(t, tc.Context().ConvLoader.Queue(context.TODO(),
   272  		types.NewConvLoaderJob(res.ConvID, &chat1.Pagination{Num: 1}, types.ConvLoaderPriorityHigh,
   273  			types.ConvLoaderGeneric, newConvLoaderPagebackHook(tc.Context(), 0, 1))))
   274  	for i := 0; i < 2; i++ {
   275  		select {
   276  		case <-listener.bgConvLoads:
   277  		case <-time.After(20 * time.Second):
   278  			require.Fail(t, "no load")
   279  		}
   280  	}
   281  }
   282  
   283  func TestConvLoaderJobQueue(t *testing.T) {
   284  	j := newJobQueue(10)
   285  	convID1 := chat1.ConversationID([]byte{1, 2, 3})
   286  	convID2 := chat1.ConversationID([]byte{1, 2, 3, 4})
   287  	newTask := func(convID chat1.ConversationID, p types.ConvLoaderPriority,
   288  		u types.ConvLoaderUniqueness) clTask {
   289  		job := types.NewConvLoaderJob(convID, nil, p, u, nil)
   290  		return clTask{job: job}
   291  	}
   292  
   293  	t.Logf("test wait")
   294  	select {
   295  	case <-j.Wait():
   296  		require.Fail(t, "queue empty")
   297  	default:
   298  	}
   299  	cb := make(chan bool, 1)
   300  	go func() {
   301  		ret := true
   302  		select {
   303  		case <-j.Wait():
   304  		case <-time.After(20 * time.Second):
   305  			ret = false
   306  		}
   307  		cb <- ret
   308  	}()
   309  	time.Sleep(100 * time.Millisecond)
   310  	_, err := j.Push(newTask(convID1, types.ConvLoaderPriorityLow, types.ConvLoaderGeneric))
   311  	require.NoError(t, err)
   312  	require.True(t, <-cb)
   313  	task, ok := j.PopFront()
   314  	require.True(t, ok)
   315  	require.Equal(t, types.ConvLoaderPriorityLow, task.job.Priority)
   316  	require.Zero(t, j.queue.Len())
   317  
   318  	t.Logf("test priority")
   319  	order := []types.ConvLoaderPriority{types.ConvLoaderPriorityHigh, types.ConvLoaderPriorityMedium,
   320  		types.ConvLoaderPriorityLow, types.ConvLoaderPriorityLow}
   321  	for i := len(order) - 1; i >= 0; i-- {
   322  		_, err = j.Push(newTask(convID1, order[i], types.ConvLoaderUnique))
   323  		require.NoError(t, err)
   324  	}
   325  	for i := 0; i < len(order); i++ {
   326  		task, ok := j.PopFront()
   327  		require.True(t, ok)
   328  		require.Equal(t, order[i], task.job.Priority)
   329  
   330  	}
   331  	require.Zero(t, j.queue.Len())
   332  
   333  	t.Logf("test dupe checking")
   334  	queued, err := j.Push(clTask{job: types.NewConvLoaderJob(convID1, nil, types.ConvLoaderPriorityHigh,
   335  		types.ConvLoaderGeneric, nil)})
   336  	require.NoError(t, err)
   337  	require.True(t, queued)
   338  	queued, err = j.Push(clTask{job: types.NewConvLoaderJob(convID2, nil, types.ConvLoaderPriorityHigh,
   339  		types.ConvLoaderGeneric, nil)})
   340  	require.NoError(t, err)
   341  	require.True(t, queued)
   342  	queued, err = j.Push(clTask{job: types.NewConvLoaderJob(convID2, &chat1.Pagination{Num: 150},
   343  		types.ConvLoaderPriorityHigh, types.ConvLoaderGeneric, nil)})
   344  	require.NoError(t, err)
   345  	require.True(t, queued)
   346  	queued, err = j.Push(clTask{job: types.NewConvLoaderJob(convID2, &chat1.Pagination{Num: 150},
   347  		types.ConvLoaderPriorityHigh, types.ConvLoaderGeneric, nil)})
   348  	require.NoError(t, err)
   349  	require.False(t, queued)
   350  	queued, err = j.Push(clTask{job: types.NewConvLoaderJob(convID2, &chat1.Pagination{Num: 150},
   351  		types.ConvLoaderPriorityHigh, types.ConvLoaderUnique, nil)})
   352  	require.NoError(t, err)
   353  	require.True(t, queued)
   354  	for i := 0; i < 4; i++ {
   355  		_, ok := j.PopFront()
   356  		require.True(t, ok)
   357  	}
   358  	require.Zero(t, j.queue.Len())
   359  	queued, err = j.Push(clTask{job: types.NewConvLoaderJob(convID2, &chat1.Pagination{Num: 150},
   360  		types.ConvLoaderPriorityHigh, types.ConvLoaderGeneric, nil)})
   361  	require.NoError(t, err)
   362  	require.True(t, queued)
   363  	_, ok = j.PopFront()
   364  	require.True(t, ok)
   365  	require.Zero(t, j.queue.Len())
   366  
   367  	t.Logf("test maxsize")
   368  	j = newJobQueue(2)
   369  	_, err = j.Push(newTask(convID1, types.ConvLoaderPriorityLow, types.ConvLoaderUnique))
   370  	require.NoError(t, err)
   371  	_, err = j.Push(newTask(convID1, types.ConvLoaderPriorityLow, types.ConvLoaderUnique))
   372  	require.NoError(t, err)
   373  	_, err = j.Push(newTask(convID1, types.ConvLoaderPriorityLow, types.ConvLoaderUnique))
   374  	require.Error(t, err)
   375  }