1/* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANYf KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17import {assertDefined} from 'common/assert_utils'; 18import {InMemoryStorage} from 'common/in_memory_storage'; 19import {TracePositionUpdate} from 'messaging/winscope_event'; 20import {TimestampConverterUtils} from 'test/unit/timestamp_converter_utils'; 21import {TraceBuilder} from 'test/unit/trace_builder'; 22import {TreeNodeUtils} from 'test/unit/tree_node_utils'; 23import {UnitTestUtils} from 'test/unit/utils'; 24import {Parser} from 'trace/parser'; 25import {Trace} from 'trace/trace'; 26import {PropertyTreeNode} from 'trace/tree_node/property_tree_node'; 27import {Presenter} from './presenter'; 28import {UiData, UiDataEntryType} from './ui_data'; 29 30describe('PresenterTransactions', () => { 31 let parser: Parser<PropertyTreeNode>; 32 let trace: Trace<PropertyTreeNode>; 33 let presenter: Presenter; 34 let outputUiData: undefined | UiData; 35 const TOTAL_OUTPUT_ENTRIES = 1647; 36 37 beforeAll(async () => { 38 jasmine.addCustomEqualityTester(TreeNodeUtils.treeNodeEqualityTester); 39 parser = (await UnitTestUtils.getParser( 40 'traces/elapsed_and_real_timestamp/Transactions.pb', 41 )) as Parser<PropertyTreeNode>; 42 }); 43 44 beforeEach(async () => { 45 outputUiData = undefined; 46 await setUpTestEnvironment(); 47 }); 48 49 it('is robust to empty trace', async () => { 50 const trace = new TraceBuilder<PropertyTreeNode>().setEntries([]).build(); 51 presenter = new Presenter(trace, new InMemoryStorage(), (data: UiData) => { 52 outputUiData = data; 53 }); 54 55 expect(outputUiData).toEqual(UiData.EMPTY); 56 57 const expectedUiData = UiData.EMPTY; 58 expectedUiData.propertiesUserOptions = { 59 showDefaults: { 60 name: 'Show defaults', 61 enabled: false, 62 tooltip: ` 63 If checked, shows the value of all properties. 64 Otherwise, hides all properties whose value is 65 the default for its data type. 66 `, 67 }, 68 }; 69 await presenter.onAppEvent( 70 TracePositionUpdate.fromTimestamp( 71 TimestampConverterUtils.makeRealTimestamp(10n), 72 ), 73 ); 74 expect(outputUiData).toEqual(UiData.EMPTY); 75 }); 76 77 it('processes trace position update and computes output UI data', async () => { 78 await presenter.onAppEvent(createTracePositionUpdate(0)); 79 checkInitialTracePositionUpdate(); 80 81 expect(assertDefined(outputUiData).allPids).toEqual([ 82 'N/A', 83 '0', 84 '515', 85 '1593', 86 '2022', 87 '2322', 88 '2463', 89 '3300', 90 ]); 91 expect(assertDefined(outputUiData).allUids).toEqual([ 92 'N/A', 93 '1000', 94 '1003', 95 '10169', 96 '10235', 97 '10239', 98 ]); 99 expect(assertDefined(outputUiData).allTypes).toEqual([ 100 'DISPLAY_CHANGED', 101 'LAYER_ADDED', 102 'LAYER_CHANGED', 103 'LAYER_DESTROYED', 104 'LAYER_HANDLE_DESTROYED', 105 'NO_OP', 106 ]); 107 108 expect(assertDefined(outputUiData).allTransactionIds.length).toEqual(1295); 109 expect(assertDefined(outputUiData).allLayerAndDisplayIds.length).toEqual( 110 117, 111 ); 112 expect(assertDefined(outputUiData).entries.length).toEqual( 113 TOTAL_OUTPUT_ENTRIES, 114 ); 115 }); 116 117 it('processes trace position update and updates current entry and scroll position', async () => { 118 await presenter.onAppEvent(createTracePositionUpdate(0)); 119 checkInitialTracePositionUpdate(); 120 121 await presenter.onAppEvent(createTracePositionUpdate(10)); 122 expect(assertDefined(outputUiData).currentEntryIndex).toEqual(13); 123 expect(assertDefined(outputUiData).scrollToIndex).toEqual(13); 124 }); 125 126 it('filters entries according to transaction ID filter', () => { 127 presenter.onTransactionIdFilterChanged([]); 128 expect(assertDefined(outputUiData).entries.length).toEqual( 129 TOTAL_OUTPUT_ENTRIES, 130 ); 131 132 presenter.onTransactionIdFilterChanged(['2211908157465']); 133 expect( 134 new Set( 135 assertDefined(outputUiData).entries.map((entry) => entry.transactionId), 136 ), 137 ).toEqual(new Set(['2211908157465'])); 138 }); 139 140 it('filters entries according to VSYNC ID filter', () => { 141 presenter.onVSyncIdFilterChanged([]); 142 expect(assertDefined(outputUiData).entries.length).toEqual( 143 TOTAL_OUTPUT_ENTRIES, 144 ); 145 146 presenter.onVSyncIdFilterChanged(['1']); 147 expect( 148 new Set( 149 assertDefined(outputUiData).entries.map((entry) => entry.vsyncId), 150 ), 151 ).toEqual(new Set([1])); 152 153 presenter.onVSyncIdFilterChanged(['1', '3', '10']); 154 expect( 155 new Set( 156 assertDefined(outputUiData).entries.map((entry) => entry.vsyncId), 157 ), 158 ).toEqual(new Set([1, 3, 10])); 159 }); 160 161 it('filters entries according to PID filter', () => { 162 presenter.onPidFilterChanged([]); 163 expect( 164 new Set(assertDefined(outputUiData).entries.map((entry) => entry.pid)), 165 ).toEqual( 166 new Set(['N/A', '0', '515', '1593', '2022', '2322', '2463', '3300']), 167 ); 168 169 presenter.onPidFilterChanged(['0']); 170 expect( 171 new Set(assertDefined(outputUiData).entries.map((entry) => entry.pid)), 172 ).toEqual(new Set(['0'])); 173 174 presenter.onPidFilterChanged(['0', '515']); 175 expect( 176 new Set(assertDefined(outputUiData).entries.map((entry) => entry.pid)), 177 ).toEqual(new Set(['0', '515'])); 178 }); 179 180 it('filters entries according to UID filter', () => { 181 presenter.onUidFilterChanged([]); 182 expect( 183 new Set(assertDefined(outputUiData).entries.map((entry) => entry.uid)), 184 ).toEqual(new Set(['N/A', '1000', '1003', '10169', '10235', '10239'])); 185 186 presenter.onUidFilterChanged(['1000']); 187 expect( 188 new Set(assertDefined(outputUiData).entries.map((entry) => entry.uid)), 189 ).toEqual(new Set(['1000'])); 190 191 presenter.onUidFilterChanged(['1000', '1003']); 192 expect( 193 new Set(assertDefined(outputUiData).entries.map((entry) => entry.uid)), 194 ).toEqual(new Set(['1000', '1003'])); 195 }); 196 197 it('filters entries according to type filter', () => { 198 presenter.onTypeFilterChanged([]); 199 expect( 200 new Set(assertDefined(outputUiData).entries.map((entry) => entry.type)), 201 ).toEqual( 202 new Set([ 203 UiDataEntryType.DISPLAY_CHANGED, 204 UiDataEntryType.LAYER_ADDED, 205 UiDataEntryType.LAYER_CHANGED, 206 UiDataEntryType.LAYER_DESTROYED, 207 UiDataEntryType.LAYER_HANDLE_DESTROYED, 208 UiDataEntryType.NO_OP, 209 ]), 210 ); 211 212 presenter.onTypeFilterChanged([UiDataEntryType.LAYER_ADDED]); 213 expect( 214 new Set(assertDefined(outputUiData).entries.map((entry) => entry.type)), 215 ).toEqual(new Set([UiDataEntryType.LAYER_ADDED])); 216 217 presenter.onTypeFilterChanged([ 218 UiDataEntryType.LAYER_ADDED, 219 UiDataEntryType.LAYER_DESTROYED, 220 ]); 221 expect( 222 new Set(assertDefined(outputUiData).entries.map((entry) => entry.type)), 223 ).toEqual( 224 new Set([UiDataEntryType.LAYER_ADDED, UiDataEntryType.LAYER_DESTROYED]), 225 ); 226 }); 227 228 it('filters entries according to layer or display ID filter', () => { 229 presenter.onLayerIdFilterChanged([]); 230 expect( 231 new Set( 232 assertDefined(outputUiData).entries.map( 233 (entry) => entry.layerOrDisplayId, 234 ), 235 ).size, 236 ).toBeGreaterThan(20); 237 238 presenter.onLayerIdFilterChanged(['1']); 239 expect( 240 new Set( 241 assertDefined(outputUiData).entries.map( 242 (entry) => entry.layerOrDisplayId, 243 ), 244 ), 245 ).toEqual(new Set(['1'])); 246 247 presenter.onLayerIdFilterChanged(['1', '3']); 248 expect( 249 new Set( 250 assertDefined(outputUiData).entries.map( 251 (entry) => entry.layerOrDisplayId, 252 ), 253 ), 254 ).toEqual(new Set(['1', '3'])); 255 }); 256 257 it('includes no op transitions', () => { 258 presenter.onTypeFilterChanged([UiDataEntryType.NO_OP]); 259 expect( 260 new Set(assertDefined(outputUiData).entries.map((entry) => entry.type)), 261 ).toEqual(new Set([UiDataEntryType.NO_OP])); 262 263 for (const entry of assertDefined(outputUiData).entries) { 264 expect(entry.layerOrDisplayId).toEqual(''); 265 expect(entry.what).toEqual(''); 266 expect(entry.propertiesTree).toEqual(undefined); 267 } 268 }); 269 270 it('filters entries according to "what" search string', () => { 271 expect(assertDefined(outputUiData).entries.length).toEqual( 272 TOTAL_OUTPUT_ENTRIES, 273 ); 274 275 presenter.onWhatFilterChanged([]); 276 expect(assertDefined(outputUiData).entries.length).toEqual( 277 TOTAL_OUTPUT_ENTRIES, 278 ); 279 280 presenter.onWhatFilterChanged(['Crop']); 281 expect(assertDefined(outputUiData).entries.length).toBeLessThan( 282 TOTAL_OUTPUT_ENTRIES, 283 ); 284 285 presenter.onWhatFilterChanged(['STRING_WITH_NO_MATCHES']); 286 expect(assertDefined(outputUiData).entries.length).toEqual(0); 287 }); 288 289 it('updates selected entry ui data when entry clicked', async () => { 290 await presenter.onAppEvent(createTracePositionUpdate(0)); 291 checkInitialTracePositionUpdate(); 292 293 const newIndex = 10; 294 presenter.onEntryClicked(newIndex); 295 checkSelectedEntryUiData(newIndex); 296 expect(assertDefined(outputUiData).scrollToIndex).toEqual(undefined); // no scrolling 297 298 // does not remove selection when entry clicked again 299 presenter.onEntryClicked(newIndex); 300 checkSelectedEntryUiData(newIndex); 301 expect(assertDefined(outputUiData).scrollToIndex).toEqual(undefined); 302 }); 303 304 it('updates selected entry ui data when entry changed by key press', async () => { 305 await presenter.onAppEvent(createTracePositionUpdate(0)); 306 checkInitialTracePositionUpdate(); 307 308 const newIndex = 10; 309 presenter.onEntryChangedByKeyPress(newIndex); 310 checkSelectedEntryUiData(newIndex); 311 expect(assertDefined(outputUiData).scrollToIndex).toEqual(newIndex); 312 }); 313 314 it('computes current entry index', async () => { 315 await presenter.onAppEvent(createTracePositionUpdate(0)); 316 expect(assertDefined(outputUiData).currentEntryIndex).toEqual(0); 317 318 await presenter.onAppEvent(createTracePositionUpdate(10)); 319 expect(assertDefined(outputUiData).currentEntryIndex).toEqual(13); 320 }); 321 322 it('updates current entry index when filters change', async () => { 323 await presenter.onAppEvent(createTracePositionUpdate(10)); 324 325 presenter.onPidFilterChanged([]); 326 expect(assertDefined(outputUiData).currentEntryIndex).toEqual(13); 327 328 presenter.onPidFilterChanged(['0']); 329 expect(assertDefined(outputUiData).currentEntryIndex).toEqual(10); 330 331 presenter.onPidFilterChanged(['0', '515']); 332 expect(assertDefined(outputUiData).currentEntryIndex).toEqual(11); 333 334 presenter.onPidFilterChanged(['0', '515', 'N/A']); 335 expect(assertDefined(outputUiData).currentEntryIndex).toEqual(13); 336 }); 337 338 it('formats entry time', async () => { 339 await setUpTestEnvironment(); 340 expect( 341 assertDefined(outputUiData).entries[0].time.formattedValue(), 342 ).toEqual('2022-08-03, 06:19:01.051'); 343 }); 344 345 async function setUpTestEnvironment() { 346 trace = new TraceBuilder<PropertyTreeNode>().setParser(parser).build(); 347 presenter = new Presenter(trace, new InMemoryStorage(), (data: UiData) => { 348 outputUiData = data; 349 }); 350 await presenter.onAppEvent(createTracePositionUpdate(0)); // trigger initialization 351 } 352 353 function createTracePositionUpdate(entryIndex: number): TracePositionUpdate { 354 const entry = trace.getEntry(entryIndex); 355 return TracePositionUpdate.fromTraceEntry(entry); 356 } 357 358 function checkInitialTracePositionUpdate() { 359 const uiData = assertDefined(outputUiData); 360 expect(uiData.currentEntryIndex).toEqual(0); 361 expect(uiData.selectedEntryIndex).toBeUndefined(); 362 expect(uiData.scrollToIndex).toEqual(0); 363 expect(assertDefined(uiData.currentPropertiesTree).id).toEqual( 364 assertDefined(uiData.entries[0].propertiesTree).id, 365 ); 366 } 367 368 function checkSelectedEntryUiData(index: number) { 369 const uiData = assertDefined(outputUiData); 370 expect(uiData.currentEntryIndex).toEqual(0); 371 expect(uiData.selectedEntryIndex).toEqual(index); 372 expect(assertDefined(uiData.currentPropertiesTree).id).toEqual( 373 assertDefined(uiData.entries[index].propertiesTree).id, 374 ); 375 } 376}); 377