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 });