go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/ui/src/common/services/luci_analysis/luci_analysis.test.ts (about)

     1  // Copyright 2022 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  import { PrpcClientExt } from '@/generic_libs/tools/prpc_client_ext';
    16  
    17  import {
    18    ClusterRequest,
    19    ClusterResponse,
    20    ClustersService,
    21  } from './luci_analysis';
    22  
    23  const clusteringVersion = {
    24    algorithmsVersion: '1',
    25    rulesVersion: '1',
    26    configVersion: '1',
    27  };
    28  
    29  describe('ClustersService', () => {
    30    test('should batch requests from the same project together', async () => {
    31      const prpc = new PrpcClientExt({}, () => '');
    32      const callStub = jest.spyOn(prpc, 'call');
    33      const clustersService = new ClustersService(prpc);
    34      const mockedRes: ClusterResponse = {
    35        clusteringVersion,
    36        clusteredTestResults: [
    37          { clusters: [{ clusterId: { algorithm: 'algorithm', id: '1' } }] },
    38          { clusters: [{ clusterId: { algorithm: 'algorithm', id: '2' } }] },
    39        ],
    40      };
    41      callStub.mockResolvedValueOnce(mockedRes);
    42  
    43      const call1 = clustersService.cluster({
    44        project: 'proj1',
    45        testResults: [{ testId: 'test1' }],
    46      });
    47      const call2 = clustersService.cluster({
    48        project: 'proj1',
    49        testResults: [{ testId: 'test2' }],
    50      });
    51      const res1 = await call1;
    52      const res2 = await call2;
    53  
    54      expect(callStub.mock.calls.length).toStrictEqual(1);
    55      expect(callStub.mock.calls[0]).toEqual([
    56        'luci.analysis.v1.Clusters',
    57        'Cluster',
    58        {
    59          project: 'proj1',
    60          testResults: [{ testId: 'test1' }, { testId: 'test2' }],
    61        },
    62      ]);
    63      expect(res1).toEqual({
    64        clusteringVersion,
    65        clusteredTestResults: [
    66          { clusters: [{ clusterId: { algorithm: 'algorithm', id: '1' } }] },
    67        ],
    68      });
    69      expect(res2).toEqual({
    70        clusteringVersion,
    71        clusteredTestResults: [
    72          { clusters: [{ clusterId: { algorithm: 'algorithm', id: '2' } }] },
    73        ],
    74      });
    75    });
    76  
    77    test('should not batch requests from different projects together', async () => {
    78      const prpc = new PrpcClientExt({}, () => '');
    79      const callStub = jest.spyOn(prpc, 'call');
    80      const clustersService = new ClustersService(prpc);
    81      const mockedRes1: ClusterResponse = {
    82        clusteringVersion,
    83        clusteredTestResults: [
    84          { clusters: [{ clusterId: { algorithm: 'algorithm', id: '1' } }] },
    85        ],
    86      };
    87      const mockedRes2: ClusterResponse = {
    88        clusteringVersion,
    89        clusteredTestResults: [
    90          { clusters: [{ clusterId: { algorithm: 'algorithm', id: '2' } }] },
    91        ],
    92      };
    93      callStub.mockResolvedValueOnce(mockedRes1);
    94      callStub.mockResolvedValueOnce(mockedRes2);
    95  
    96      const call1 = clustersService.cluster({
    97        project: 'proj1',
    98        testResults: [{ testId: 'test1' }],
    99      });
   100      const call2 = clustersService.cluster({
   101        project: 'proj2',
   102        testResults: [{ testId: 'test2' }],
   103      });
   104      const res1 = await call1;
   105      const res2 = await call2;
   106  
   107      expect(callStub.mock.calls.length).toStrictEqual(2);
   108      expect(callStub.mock.calls[0]).toEqual([
   109        'luci.analysis.v1.Clusters',
   110        'Cluster',
   111        {
   112          project: 'proj1',
   113          testResults: [{ testId: 'test1' }],
   114        },
   115      ]);
   116      expect(callStub.mock.calls[1]).toEqual([
   117        'luci.analysis.v1.Clusters',
   118        'Cluster',
   119        {
   120          project: 'proj2',
   121          testResults: [{ testId: 'test2' }],
   122        },
   123      ]);
   124      expect(res1).toEqual({
   125        clusteringVersion,
   126        clusteredTestResults: [
   127          { clusters: [{ clusterId: { algorithm: 'algorithm', id: '1' } }] },
   128        ],
   129      });
   130      expect(res2).toEqual({
   131        clusteringVersion,
   132        clusteredTestResults: [
   133          { clusters: [{ clusterId: { algorithm: 'algorithm', id: '2' } }] },
   134        ],
   135      });
   136    });
   137  
   138    test('should not batch more than 1000 requests together', async () => {
   139      const prpc = new PrpcClientExt({}, () => '');
   140      const callStub = jest.spyOn(prpc, 'call');
   141      const clustersService = new ClustersService(prpc);
   142  
   143      function makeRes(startIndex: number, count: number) {
   144        const res: DeepMutable<ClusterResponse> = {
   145          clusteringVersion,
   146          clusteredTestResults: [],
   147        };
   148        for (let i = startIndex; i < count + startIndex; ++i) {
   149          res.clusteredTestResults.push({
   150            clusters: [{ clusterId: { algorithm: 'algorithm', id: `${i}` } }],
   151          });
   152        }
   153        return res;
   154      }
   155  
   156      function makeReq(startIndex: number, count: number) {
   157        const req: DeepMutable<ClusterRequest> = {
   158          project: 'proj1',
   159          testResults: [],
   160        };
   161        for (let i = startIndex; i < count + startIndex; ++i) {
   162          req.testResults.push({ testId: `test${i}` });
   163        }
   164        return req;
   165      }
   166  
   167      const mockedRes1 = makeRes(0, 800);
   168      const mockedRes2 = makeRes(800, 400);
   169      callStub.mockResolvedValueOnce(mockedRes1);
   170      callStub.mockResolvedValueOnce(mockedRes2);
   171  
   172      const call1 = clustersService.cluster(makeReq(0, 400));
   173      const call2 = clustersService.cluster(makeReq(400, 400));
   174      const call3 = clustersService.cluster(makeReq(800, 400));
   175      const res1 = await call1;
   176      const res2 = await call2;
   177      const res3 = await call3;
   178  
   179      expect(callStub.mock.calls.length).toStrictEqual(2);
   180      expect(callStub.mock.calls[0]).toEqual([
   181        'luci.analysis.v1.Clusters',
   182        'Cluster',
   183        makeReq(0, 800),
   184      ]);
   185      expect(callStub.mock.calls[1]).toEqual([
   186        'luci.analysis.v1.Clusters',
   187        'Cluster',
   188        makeReq(800, 400),
   189      ]);
   190      expect(res1).toEqual(makeRes(0, 400));
   191      expect(res2).toEqual(makeRes(400, 400));
   192      expect(res3).toEqual(makeRes(800, 400));
   193    });
   194  });