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 {TracePositionUpdate} from 'messaging/winscope_event'; 19import {PropertyTreeBuilder} from 'test/unit/property_tree_builder'; 20import {TimestampConverterUtils} from 'test/unit/timestamp_converter_utils'; 21import {TracesBuilder} from 'test/unit/traces_builder'; 22import {TraceBuilder} from 'test/unit/trace_builder'; 23import {Trace} from 'trace/trace'; 24import {TraceType} from 'trace/trace_type'; 25import { 26 DEFAULT_PROPERTY_FORMATTER, 27 TIMESTAMP_NODE_FORMATTER, 28} from 'trace/tree_node/formatters'; 29import {PropertyTreeNode} from 'trace/tree_node/property_tree_node'; 30import {Presenter} from './presenter'; 31import {UiData, UiDataMessage} from './ui_data'; 32 33describe('ViewerProtoLogPresenter', () => { 34 let presenter: Presenter; 35 let inputMessages: UiDataMessage[]; 36 let trace: Trace<PropertyTreeNode>; 37 let positionUpdate10: TracePositionUpdate; 38 let positionUpdate11: TracePositionUpdate; 39 let positionUpdate12: TracePositionUpdate; 40 let outputUiData: undefined | UiData; 41 42 beforeEach(async () => { 43 const time10 = TimestampConverterUtils.makeRealTimestamp(10n); 44 const time11 = TimestampConverterUtils.makeRealTimestamp(11n); 45 const time12 = TimestampConverterUtils.makeRealTimestamp(12n); 46 const elapsedTime10 = TimestampConverterUtils.makeElapsedTimestamp(10n); 47 const elapsedTime20 = TimestampConverterUtils.makeElapsedTimestamp(20n); 48 const elapsedTime30 = TimestampConverterUtils.makeElapsedTimestamp(30n); 49 50 const entries = [ 51 new PropertyTreeBuilder() 52 .setRootId('ProtologTrace') 53 .setName('message') 54 .setChildren([ 55 {name: 'text', value: 'text0', formatter: DEFAULT_PROPERTY_FORMATTER}, 56 { 57 name: 'timestamp', 58 value: elapsedTime10, 59 formatter: TIMESTAMP_NODE_FORMATTER, 60 }, 61 {name: 'tag', value: 'tag0', formatter: DEFAULT_PROPERTY_FORMATTER}, 62 { 63 name: 'level', 64 value: 'level0', 65 formatter: DEFAULT_PROPERTY_FORMATTER, 66 }, 67 { 68 name: 'at', 69 value: 'sourcefile0', 70 formatter: DEFAULT_PROPERTY_FORMATTER, 71 }, 72 ]) 73 .build(), 74 75 new PropertyTreeBuilder() 76 .setRootId('ProtologTrace') 77 .setName('message') 78 .setChildren([ 79 {name: 'text', value: 'text1', formatter: DEFAULT_PROPERTY_FORMATTER}, 80 { 81 name: 'timestamp', 82 value: elapsedTime20, 83 formatter: TIMESTAMP_NODE_FORMATTER, 84 }, 85 {name: 'tag', value: 'tag1', formatter: DEFAULT_PROPERTY_FORMATTER}, 86 { 87 name: 'level', 88 value: 'level1', 89 formatter: DEFAULT_PROPERTY_FORMATTER, 90 }, 91 { 92 name: 'at', 93 value: 'sourcefile1', 94 formatter: DEFAULT_PROPERTY_FORMATTER, 95 }, 96 ]) 97 .build(), 98 99 new PropertyTreeBuilder() 100 .setRootId('ProtologTrace') 101 .setName('message') 102 .setChildren([ 103 {name: 'text', value: 'text2', formatter: DEFAULT_PROPERTY_FORMATTER}, 104 { 105 name: 'timestamp', 106 value: elapsedTime30, 107 formatter: TIMESTAMP_NODE_FORMATTER, 108 }, 109 {name: 'tag', value: 'tag2', formatter: DEFAULT_PROPERTY_FORMATTER}, 110 { 111 name: 'level', 112 value: 'level2', 113 formatter: DEFAULT_PROPERTY_FORMATTER, 114 }, 115 { 116 name: 'at', 117 value: 'sourcefile2', 118 formatter: DEFAULT_PROPERTY_FORMATTER, 119 }, 120 ]) 121 .build(), 122 ]; 123 124 inputMessages = [ 125 { 126 traceIndex: 0, 127 text: 'text0', 128 time: assertDefined(entries[0].getChildByName('timestamp')), 129 tag: 'tag0', 130 level: 'level0', 131 at: 'sourcefile0', 132 }, 133 { 134 traceIndex: 1, 135 text: 'text1', 136 time: assertDefined(entries[1].getChildByName('timestamp')), 137 tag: 'tag1', 138 level: 'level1', 139 at: 'sourcefile1', 140 }, 141 { 142 traceIndex: 2, 143 text: 'text2', 144 time: assertDefined(entries[2].getChildByName('timestamp')), 145 tag: 'tag2', 146 level: 'level2', 147 at: 'sourcefile2', 148 }, 149 ]; 150 151 trace = new TraceBuilder<PropertyTreeNode>() 152 .setEntries(entries) 153 .setTimestamps([time10, time11, time12]) 154 .build(); 155 156 positionUpdate10 = TracePositionUpdate.fromTimestamp(time10); 157 positionUpdate11 = TracePositionUpdate.fromTimestamp(time11); 158 positionUpdate12 = TracePositionUpdate.fromTimestamp(time12); 159 160 outputUiData = undefined; 161 162 presenter = new Presenter(trace, (data: UiData) => { 163 outputUiData = data; 164 }); 165 await presenter.onAppEvent(positionUpdate10); // trigger initialization 166 }); 167 168 it('is robust to empty trace', async () => { 169 const traces = new TracesBuilder() 170 .setEntries(TraceType.PROTO_LOG, []) 171 .build(); 172 const trace = new TraceBuilder<PropertyTreeNode>().setEntries([]).build(); 173 presenter = new Presenter(trace, (data: UiData) => { 174 outputUiData = data; 175 }); 176 177 const uiData = assertDefined(outputUiData); 178 expect(uiData.messages).toEqual([]); 179 expect(uiData.currentMessageIndex).toBeUndefined(); 180 181 await presenter.onAppEvent(positionUpdate10); 182 183 const newUiData = assertDefined(outputUiData); 184 expect(newUiData.messages).toEqual([]); 185 expect(newUiData.currentMessageIndex).toBeUndefined(); 186 }); 187 188 it('processes trace position updates', async () => { 189 await presenter.onAppEvent(positionUpdate10); 190 191 const uiData = assertDefined(outputUiData); 192 expect(uiData.allLogLevels).toEqual(['level0', 'level1', 'level2']); 193 expect(uiData.allTags).toEqual(['tag0', 'tag1', 'tag2']); 194 expect(uiData.allSourceFiles).toEqual([ 195 'sourcefile0', 196 'sourcefile1', 197 'sourcefile2', 198 ]); 199 expect(uiData.messages).toEqual(inputMessages); 200 expect(uiData.currentMessageIndex).toEqual(0); 201 }); 202 203 it('updates displayed messages according to log levels filter', () => { 204 expect(assertDefined(outputUiData).messages).toEqual(inputMessages); 205 206 presenter.onLogLevelsFilterChanged([]); 207 expect(assertDefined(outputUiData).messages).toEqual(inputMessages); 208 209 presenter.onLogLevelsFilterChanged(['level1']); 210 expect(assertDefined(outputUiData).messages).toEqual([inputMessages[1]]); 211 212 presenter.onLogLevelsFilterChanged(['level0', 'level1', 'level2']); 213 expect(assertDefined(outputUiData).messages).toEqual(inputMessages); 214 }); 215 216 it('updates displayed messages according to tags filter', () => { 217 expect(assertDefined(outputUiData).messages).toEqual(inputMessages); 218 219 presenter.onTagsFilterChanged([]); 220 expect(assertDefined(outputUiData).messages).toEqual(inputMessages); 221 222 presenter.onTagsFilterChanged(['tag1']); 223 expect(assertDefined(outputUiData).messages).toEqual([inputMessages[1]]); 224 225 presenter.onTagsFilterChanged(['tag0', 'tag1', 'tag2']); 226 expect(assertDefined(outputUiData).messages).toEqual(inputMessages); 227 }); 228 229 it('updates displayed messages according to source files filter', () => { 230 expect(assertDefined(outputUiData).messages).toEqual(inputMessages); 231 232 presenter.onSourceFilesFilterChanged([]); 233 expect(assertDefined(outputUiData).messages).toEqual(inputMessages); 234 235 presenter.onSourceFilesFilterChanged(['sourcefile1']); 236 expect(assertDefined(outputUiData).messages).toEqual([inputMessages[1]]); 237 238 presenter.onSourceFilesFilterChanged([ 239 'sourcefile0', 240 'sourcefile1', 241 'sourcefile2', 242 ]); 243 expect(assertDefined(outputUiData).messages).toEqual(inputMessages); 244 }); 245 246 it('updates displayed messages according to search string filter', () => { 247 expect(assertDefined(outputUiData).messages).toEqual(inputMessages); 248 249 presenter.onSearchStringFilterChanged(''); 250 expect(assertDefined(outputUiData).messages).toEqual(inputMessages); 251 252 presenter.onSearchStringFilterChanged('text'); 253 expect(assertDefined(outputUiData).messages).toEqual(inputMessages); 254 255 presenter.onSearchStringFilterChanged('text0'); 256 expect(assertDefined(outputUiData).messages).toEqual([inputMessages[0]]); 257 258 presenter.onSearchStringFilterChanged('text1'); 259 expect(assertDefined(outputUiData).messages).toEqual([inputMessages[1]]); 260 }); 261 262 it('computes current message index', async () => { 263 // Position -> entry #0 264 await presenter.onAppEvent(positionUpdate10); 265 presenter.onLogLevelsFilterChanged([]); 266 expect(assertDefined(outputUiData).currentMessageIndex).toEqual(0); 267 268 presenter.onLogLevelsFilterChanged(['level0']); 269 expect(assertDefined(outputUiData).currentMessageIndex).toEqual(0); 270 271 presenter.onLogLevelsFilterChanged([]); 272 expect(assertDefined(outputUiData).currentMessageIndex).toEqual(0); 273 274 // Position -> entry #1 275 await presenter.onAppEvent(positionUpdate11); 276 presenter.onLogLevelsFilterChanged([]); 277 expect(assertDefined(outputUiData).currentMessageIndex).toEqual(1); 278 279 presenter.onLogLevelsFilterChanged(['level0']); 280 expect(assertDefined(outputUiData).currentMessageIndex).toEqual(0); 281 282 presenter.onLogLevelsFilterChanged(['level1']); 283 expect(assertDefined(outputUiData).currentMessageIndex).toEqual(0); 284 285 presenter.onLogLevelsFilterChanged(['level0', 'level1']); 286 expect(assertDefined(outputUiData).currentMessageIndex).toEqual(1); 287 288 // Position -> entry #2 289 await presenter.onAppEvent(positionUpdate12); 290 presenter.onLogLevelsFilterChanged([]); 291 expect(assertDefined(outputUiData).currentMessageIndex).toEqual(2); 292 }); 293 294 it('updates selected message index', () => { 295 expect(assertDefined(outputUiData).selectedMessageIndex).toBeUndefined(); 296 presenter.onMessageClicked(3); 297 expect(assertDefined(outputUiData).selectedMessageIndex).toEqual(3); 298 }); 299}); 300