1/* 2 * Copyright (C) 2024 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 {ParserBuilder} from 'test/unit/parser_builder'; 21import {PropertyTreeBuilder} from 'test/unit/property_tree_builder'; 22import {TimestampConverterUtils} from 'test/unit/timestamp_converter_utils'; 23import {TracesBuilder} from 'test/unit/traces_builder'; 24import {TraceBuilder} from 'test/unit/trace_builder'; 25import {UnitTestUtils} from 'test/unit/utils'; 26import {Trace} from 'trace/trace'; 27import {Traces} from 'trace/traces'; 28import {TraceType} from 'trace/trace_type'; 29import {PropertyTreeNode} from 'trace/tree_node/property_tree_node'; 30import {NotifyLogViewCallbackType} from 'viewers/common/abstract_log_viewer_presenter'; 31import {AbstractLogViewerPresenterTest} from 'viewers/common/abstract_log_viewer_presenter_test'; 32import {LogSelectFilter} from 'viewers/common/log_filters'; 33import {LogHeader, UiDataLog} from 'viewers/common/ui_data_log'; 34import {Presenter} from './presenter'; 35import {UiData} from './ui_data'; 36 37class PresenterTransitionsTest extends AbstractLogViewerPresenterTest<UiData> { 38 override readonly expectedHeaders = [ 39 { 40 header: new LogHeader({ 41 name: 'Id', 42 cssClass: 'transition-id right-align', 43 }), 44 }, 45 { 46 header: new LogHeader( 47 {name: 'Type', cssClass: 'transition-type'}, 48 new LogSelectFilter(Array.from({length: 2}, () => '')), 49 ), 50 options: ['OPEN', 'TO_FRONT'], 51 }, 52 {header: new LogHeader({name: 'Send Time', cssClass: 'send-time time'})}, 53 { 54 header: new LogHeader({ 55 name: 'Dispatch Time', 56 cssClass: 'dispatch-time time', 57 }), 58 }, 59 { 60 header: new LogHeader({ 61 name: 'Duration', 62 cssClass: 'duration right-align', 63 }), 64 }, 65 { 66 header: new LogHeader( 67 {name: 'Handler', cssClass: 'handler'}, 68 new LogSelectFilter(Array.from({length: 3}, () => '')), 69 ), 70 options: [ 71 'N/A', 72 'com.android.wm.shell.recents.RecentsTransitionHandler', 73 'com.android.wm.shell.transition.DefaultMixedHandler', 74 ], 75 }, 76 { 77 header: new LogHeader( 78 {name: 'Participants', cssClass: 'participants'}, 79 new LogSelectFilter( 80 Array.from({length: 12}, () => ''), 81 true, 82 '250', 83 '100%', 84 ), 85 ), 86 options: [ 87 '47', 88 '67', 89 '398', 90 '471', 91 '472', 92 '489', 93 '0xc3df4d', 94 '0x5ba3da0', 95 '0x97b5518', 96 '0xa884527', 97 '0xb887160', 98 '0xc5f6ee4', 99 ], 100 }, 101 { 102 header: new LogHeader( 103 {name: 'Flags', cssClass: 'flags'}, 104 new LogSelectFilter( 105 Array.from({length: 2}, () => ''), 106 true, 107 '250', 108 '100%', 109 ), 110 ), 111 options: ['TRANSIT_FLAG_IS_RECENTS', '0'], 112 }, 113 { 114 header: new LogHeader( 115 {name: 'Status', cssClass: 'status right-align'}, 116 new LogSelectFilter(Array.from({length: 3}, () => '')), 117 ), 118 options: ['MERGED', 'N/A', 'PLAYED'], 119 }, 120 ]; 121 private trace: Trace<PropertyTreeNode> | undefined; 122 private positionUpdate: TracePositionUpdate | undefined; 123 124 override async setUpTestEnvironment(): Promise<void> { 125 const parser = await UnitTestUtils.getPerfettoParser( 126 TraceType.TRANSITION, 127 'traces/perfetto/shell_transitions_trace.perfetto-trace', 128 ); 129 130 this.trace = new TraceBuilder<PropertyTreeNode>() 131 .setType(TraceType.TRANSITION) 132 .setParser(parser) 133 .build(); 134 135 this.positionUpdate = TracePositionUpdate.fromTraceEntry( 136 this.trace.getEntry(0), 137 ); 138 } 139 140 override async createPresenterWithEmptyTrace( 141 callback: NotifyLogViewCallbackType<UiData>, 142 ): Promise<Presenter> { 143 const traces = new TracesBuilder() 144 .setEntries(TraceType.TRANSITION, []) 145 .build(); 146 const trace = assertDefined(traces.getTrace(TraceType.TRANSITION)); 147 return new Presenter(trace, traces, new InMemoryStorage(), callback); 148 } 149 150 override async createPresenter( 151 callback: NotifyLogViewCallbackType<UiData>, 152 trace = this.trace, 153 positionUpdate = assertDefined(this.getPositionUpdate()), 154 ): Promise<Presenter> { 155 const transitionTrace = assertDefined(trace); 156 const traces = new Traces(); 157 traces.addTrace(transitionTrace); 158 159 const presenter = new Presenter( 160 transitionTrace, 161 traces, 162 new InMemoryStorage(), 163 callback, 164 ); 165 await presenter.onAppEvent(positionUpdate); // trigger initialization 166 return presenter; 167 } 168 169 override getPositionUpdate(): TracePositionUpdate { 170 return assertDefined(this.positionUpdate); 171 } 172 173 override executePropertiesChecksAfterPositionUpdate(uiData: UiDataLog) { 174 expect(uiData.entries.length).toEqual(4); 175 176 const selectedTransition = assertDefined(uiData.propertiesTree); 177 const wmData = assertDefined(selectedTransition.getChildByName('wmData')); 178 expect(wmData.getChildByName('id')?.formattedValue()).toEqual('35'); 179 expect(wmData.getChildByName('type')?.formattedValue()).toEqual('OPEN'); 180 expect(wmData.getChildByName('createTimeNs')?.formattedValue()).toEqual( 181 '2023-11-21, 13:30:33.176', 182 ); 183 184 const dispatchTime = uiData.entries[0].fields[3]; 185 expect(dispatchTime?.propagateEntryTimestamp).toBeTrue(); 186 } 187 188 override executeSpecializedTests() { 189 describe('Specialized tests', () => { 190 it('robust to corrupted transitions trace', async () => { 191 const timestamp10 = TimestampConverterUtils.makeRealTimestamp(10n); 192 const trace = new TraceBuilder<PropertyTreeNode>() 193 .setType(TraceType.TRANSITION) 194 .setParser( 195 new ParserBuilder<PropertyTreeNode>() 196 .setIsCorrupted(true) 197 .setEntries([ 198 new PropertyTreeBuilder() 199 .setRootId('TransitionsTraceEntry') 200 .setName('transition0') 201 .build(), 202 ]) 203 .setTimestamps([timestamp10]) 204 .build(), 205 ) 206 .build(); 207 const positionUpdate = TracePositionUpdate.fromTimestamp(timestamp10); 208 let uiData: UiData | undefined; 209 const presenter = await this.createPresenter( 210 (newData) => { 211 uiData = newData; 212 }, 213 trace, 214 positionUpdate, 215 ); 216 await presenter.onAppEvent(positionUpdate); 217 expect(uiData?.entries).toEqual([]); 218 }); 219 }); 220 } 221} 222 223describe('PresenterTransitions', () => { 224 new PresenterTransitionsTest().execute(); 225}); 226