1 <!DOCTYPE HTML> 2 <html> 3 <!-- 4 Copyright (c) 2012 The Chromium Authors. All rights reserved. 5 Use of this source code is governed by a BSD-style license that can be 6 found in the LICENSE file. 7 --> 8 <head i18n-values="dir:textdirection;"> 9 <title>TimelineTrack tests</title> 10 <script 11 src="http://closure-library.googlecode.com/svn/trunk/closure/goog/base.js"> 12 </script> 13 <script> 14 goog.require('goog.testing.jsunit'); 15 </script> 16 <style> 17 * { 18 box-sizing: border-box; 19 -webkit-user-select: none; 20 } 21 22 .timeline-container { 23 border: 1px solid red; 24 } 25 26 </style> 27 <link rel="stylesheet" href="timeline.css"> 28 <script src="../shared/js/cr.js"></script> 29 <script src="../shared/js/cr/event_target.js"></script> 30 <script src="../shared/js/cr/ui.js"></script> 31 <script src="../shared/js/util.js"></script> 32 <script src="timeline_model.js"></script> 33 <script src="sorted_array_utils.js"></script> 34 <script src="measuring_stick.js"></script> 35 <script src="timeline.js"></script> 36 <script src="timeline_track.js"></script> 37 <script src="fast_rect_renderer.js"></script> 38 </head> 39 <body> 40 <script> 41 </script> 42 <script> 43 'use strict'; 44 45 var TimelineAsyncSlice = tracing.TimelineAsyncSlice; 46 var TimelineAsyncSliceGroup = tracing.TimelineAsyncSliceGroup; 47 var TimelineCounter = tracing.TimelineCounter; 48 var TimelineCounterTrack = tracing.TimelineCounterTrack; 49 var TimelineCpu = tracing.TimelineCpu; 50 var TimelineCpuTrack = tracing.TimelineCpuTrack; 51 var TimelineProcess = tracing.TimelineProcess; 52 var TimelineSelection = tracing.TimelineSelection; 53 var TimelineSliceTrack = tracing.TimelineSliceTrack; 54 var TimelineSlice = tracing.TimelineSlice; 55 var TimelineThread = tracing.TimelineThread; 56 var TimelineThreadSlice = tracing.TimelineThreadSlice; 57 var TimelineThreadTrack = tracing.TimelineThreadTrack; 58 var TimelineViewport = tracing.TimelineViewport; 59 var testDivs = {}; 60 61 // Helper function to create a slice. 62 function newAsyncSlice(start, duration, startThread, endThread) { 63 var s = new TimelineAsyncSlice('a', 0, start); 64 s.duration = duration; 65 s.startThread = startThread; 66 s.endThread = endThread; 67 return s; 68 } 69 70 function getTestDiv(name) { 71 if (!testDivs[name]) { 72 testDivs[name] = document.createElement('div'); 73 document.body.appendChild(testDivs[name]); 74 } 75 testDivs[name].textContent = ''; 76 return testDivs[name]; 77 } 78 79 function testBasicSlices() { 80 var testEl = getTestDiv('testBasicSlices'); 81 var track = TimelineSliceTrack(); 82 testEl.appendChild(track); 83 track.heading = 'testBasicSlices'; 84 track.slices = [ 85 new TimelineSlice('a', 0, 1, {}, 1), 86 new TimelineSlice('b', 1, 2.1, {}, 4.8), 87 new TimelineSlice('b', 1, 7, {}, 0.5), 88 new TimelineSlice('c', 2, 7.6, {}, 0.4) 89 ]; 90 track.viewport = new TimelineViewport(testEl); 91 track.viewport.xSetWorldRange(0, 8.8, track.clientWidth); 92 } 93 94 function testFindAllObjectsMatchingInSliceTrack() { 95 var track = TimelineSliceTrack(); 96 track.slices = [ 97 new TimelineSlice('a', 0, 1, {}, 1), 98 new TimelineSlice('b', 1, 2.1, {}, 4.8), 99 new TimelineSlice('b', 1, 7, {}, 0.5), 100 new TimelineSlice('c', 2, 7.6, {}, 0.4) 101 ]; 102 var selection = new TimelineSelection(); 103 track.addAllObjectsMatchingFilterToSelection( 104 new tracing.TimelineFilter("b"), selection); 105 106 assertEquals(2, selection.length); 107 assertEquals(track.slices[1], selection[0].slice); 108 assertEquals(track.slices[2], selection[1].slice); 109 } 110 111 function testShrinkingSliceSizes() { 112 var testEl = getTestDiv('testShrinkingSliceSizes'); 113 var track = TimelineSliceTrack(); 114 testEl.appendChild(track); 115 track.heading = 'testShrinkingSliceSizes'; 116 var x = 0; 117 var widths = [10, 5, 4, 3, 2, 1, 0.5, 0.4, 0.3, 0.2, 0.1, 0.05]; 118 var slices = []; 119 for (var i = 0; i < widths.length; i++) { 120 var s = new TimelineSlice('a', 1, x, {}, widths[i]); 121 x += s.duration + 0.5; 122 slices.push(s); 123 } 124 track.slices = slices; 125 track.viewport = new TimelineViewport(testEl); 126 track.viewport.xSetWorldRange(0, 1.1 * x, track.clientWidth); 127 } 128 129 function testSelectionHitTesting() { 130 var testEl = getTestDiv('testSelectionHitTesting'); 131 var track = new TimelineSliceTrack(); 132 testEl.appendChild(track); 133 track.heading = 'testSelectionHitTesting'; 134 track.headingWidth = '100px'; 135 track.slices = [ 136 new TimelineSlice('a', 0, 1, {}, 1), 137 new TimelineSlice('b', 1, 2.1, {}, 4.8) 138 ]; 139 track.style.width = '500px'; 140 track.viewport = new TimelineViewport(testEl); 141 track.viewport.xSetWorldRange(0, 7.6, track.clientWidth); 142 var clientRect = track.getBoundingClientRect(); 143 144 var selection = new TimelineSelection(); 145 track.addIntersectingItemsToSelection(1.5, clientRect.top + 5, selection); 146 assertEquals(track.slices[0], selection[0].slice); 147 148 var selection = new TimelineSelection(); 149 track.addIntersectingItemsToSelection(2, clientRect.top + 5, selection); 150 assertEquals(0, selection.length); 151 152 var selection = new TimelineSelection(); 153 track.addIntersectingItemsToSelection(6.8, clientRect.top + 5, selection); 154 assertEquals(track.slices[1], selection[0].slice); 155 156 var selection = new TimelineSelection(); 157 track.addIntersectingItemsToSelection(6.9, clientRect.top + 5, selection); 158 assertEquals(0, selection.length); 159 } 160 161 function testSelectionHitTestingWithTimelineThreadTrack() { 162 var model = new tracing.TimelineModel(); 163 var p1 = model.getOrCreateProcess(1); 164 var t1 = p1.getOrCreateThread(1); 165 t1.subRows[0].push(new tracing.TimelineThreadSlice('a', 0, 1, {}, 5)); 166 t1.subRows[0].push(new tracing.TimelineThreadSlice('b', 0, 5.1, {}, 4)); 167 168 var testEl = getTestDiv('testSelectionHitTestingWithTimelineThreadTrack'); 169 var track = new tracing.TimelineThreadTrack(); 170 testEl.appendChild(track); 171 track.heading = 'testSelectionHitTestingWithTimelineThreadTrack'; 172 track.headingWidth = '100px'; 173 track.thread = t1; 174 175 track.style.width = '500px'; 176 track.viewport = new TimelineViewport(testEl); 177 track.viewport.xSetWorldRange(0, 10, track.clientWidth); 178 var clientRect = track.getBoundingClientRect(); 179 180 var selection = new TimelineSelection(); 181 track.addIntersectingItemsToSelection(1.5, clientRect.top + 5, selection); 182 assertEquals(t1.subRows[0][0], selection[0].slice); 183 184 var selection = new TimelineSelection(); 185 track.addIntersectingItemsInRangeToSelection(1.5, 1.8, clientRect.top + 5, clientRect.top + 7, selection); 186 assertEquals(t1.subRows[0][0], selection[0].slice); 187 } 188 189 function testBasicCpu() { 190 var testEl = getTestDiv('testBasicCpu'); 191 192 var cpu = new TimelineCpu(7); 193 cpu.slices = [ 194 new TimelineSlice('a', 0, 1, {}, 1), 195 new TimelineSlice('b', 1, 2.1, {}, 4.8) 196 ]; 197 cpu.updateBounds(); 198 199 var track = TimelineCpuTrack(); 200 testEl.appendChild(track); 201 track.heading = 'CPU ' + cpu.cpuNumber; 202 track.cpu = cpu; 203 track.viewport = new TimelineViewport(testEl); 204 track.viewport.xSetWorldRange(0, 11.1, track.clientWidth); 205 } 206 207 function testViewport() { 208 var testEl = getTestDiv('testViewport'); 209 210 var track = tracing.TimelineViewportTrack(); 211 testEl.appendChild(track); 212 track.viewport = new TimelineViewport(testEl); 213 track.viewport.setPanAndScale(0, 214 track.clientWidth / 1000); 215 } 216 217 function testBasicCounter() { 218 var testEl = getTestDiv('testBasicCounter'); 219 220 var ctr = new TimelineCounter(undefined, 221 'testBasicCounter', 'testBasicCounter'); 222 ctr.seriesNames = ['value1', 'value2']; 223 ctr.seriesColors = [tracing.getStringColorId('testBasicCounter.value1'), 224 tracing.getStringColorId('testBasicCounter.value2')]; 225 ctr.timestamps = [0, 1, 2, 3, 4, 5, 6, 7]; 226 ctr.samples = [0, 5, 227 3, 3, 228 1, 1, 229 2, 1.1, 230 3, 0, 231 1, 7, 232 3, 0, 233 3.1, 0.5]; 234 ctr.updateBounds(); 235 236 var track = new TimelineCounterTrack(); 237 testEl.appendChild(track); 238 track.heading = ctr.name; 239 track.counter = ctr; 240 track.viewport = new TimelineViewport(testEl); 241 track.viewport.xSetWorldRange(0, 7.7, track.clientWidth); 242 } 243 244 function runOffscreenCounterTest(timestamps, samples, testFn) { 245 var testEl = document.createElement('div'); 246 var ctr = new TimelineCounter(undefined, 247 'foo', 'foo'); 248 var n = samples.length / timestamps.length; 249 ctr.timestamps = timestamps; 250 ctr.samples = samples; 251 ctr.seriesNames = [] 252 ctr.seriesColors = [] 253 for (var i = 0; i < n; ++i) { 254 ctr.seriesNames.push('value' + i); 255 ctr.seriesColors.push(tracing.getStringColorId(ctr.seriesNames[i])); 256 } 257 ctr.updateBounds(); 258 259 var track = new TimelineCounterTrack(); 260 testEl.appendChild(track); 261 document.body.appendChild(testEl); 262 263 track.heading = ctr.name; 264 track.counter = ctr; 265 track.viewport = new TimelineViewport(testEl); 266 track.viewport.xSetWorldRange(0, 10, track.clientWidth); 267 268 try { 269 testFn(ctr, track); 270 } finally { 271 document.body.removeChild(testEl); 272 } 273 } 274 275 function testBasicCounterXPointPicking() { 276 var timestamps = [0, 1, 2, 3, 4, 5, 6, 7]; 277 var samples = [0, 5, 278 3, 3, 279 1, 1, 280 2, 1.1, 281 3, 0, 282 1, 7, 283 3, 0, 284 3.1, 0.5]; 285 runOffscreenCounterTest(timestamps, samples, function(ctr, track) { 286 var clientRect = track.getBoundingClientRect(); 287 var y75 = clientRect.top + 0.75 * clientRect.height; 288 var sel; 289 290 // In bounds. 291 sel = new tracing.TimelineSelection(); 292 track.addIntersectingItemsToSelection(1.5, y75, sel); 293 assertEquals(1, sel.length); 294 assertEquals(track, sel[0].track); 295 assertEquals(ctr, sel[0].counter); 296 assertEquals(1, sel[0].sampleIndex); 297 298 // Outside bouds. 299 sel = new tracing.TimelineSelection(); 300 track.addIntersectingItemsToSelection(-1, y75, sel); 301 assertEquals(0, sel.length); 302 303 sel = new tracing.TimelineSelection(); 304 track.addIntersectingItemsToSelection(8, y75, sel); 305 assertEquals(0, sel.length); 306 }); 307 } 308 309 /* You'll need visual inspection to test eliding with this one. */ 310 function testElideVisualInspection() { 311 var optDicts = [{ trackName: 'elideOff', elide: false }, 312 { trackName: 'elideOn', elide: true }]; 313 for (var dictIndex in optDicts) { 314 var dict = optDicts[dictIndex]; 315 var testEl = getTestDiv(dict.trackName); 316 var track = new TimelineSliceTrack(); 317 if (dict.elide) { 318 track.SHOULD_ELIDE_TEXT = true; 319 } else { 320 track.SHOULD_ELIDE_TEXT = false; 321 } 322 var tooLongTitle = 'Unless eliding this SHOULD NOT BE DISPLAYED. '; 323 var bigTitle = 'Very big title name that goes on longer ' + 324 'than you may expect'; 325 testEl.appendChild(track); 326 track.heading = 'Visual: ' + dict.trackName; 327 track.slices = [ 328 // title, colorId, start, args, opt_duration 329 new TimelineSlice('a ' + tooLongTitle + bigTitle, 0, 1, {}, 1), 330 new TimelineSlice(bigTitle, 1, 2.1, {}, 4.8), 331 new TimelineSlice('cccc cccc cccc', 1, 7, {}, 0.5), 332 new TimelineSlice('d', 2, 7.6, {}, 1.0) 333 ]; 334 track.viewport = new TimelineViewport(testEl); 335 track.viewport.xSetWorldRange(0, 9.5, track.clientWidth); 336 } 337 } 338 339 function testElide() { 340 var testEl = getTestDiv('testElide'); 341 var track = new TimelineSliceTrack(); 342 testEl.appendChild(track); 343 var bigtitle = 'Super duper long long title ' + 344 'holy moly when did you get so verbose?'; 345 var smalltitle = 'small'; 346 track.viewport = new TimelineViewport(testEl); 347 track.heading = 'testElide'; 348 track.slices = [ 349 // title, colorId, start, args, opt_duration 350 new TimelineSlice(bigtitle, 0, 1, {}, 1), 351 new TimelineSlice(smalltitle, 1, 2, {}, 1) 352 ]; 353 track.viewport = new TimelineViewport(testEl); 354 track.viewport.xSetWorldRange(0, 3.3, track.clientWidth); 355 var stringWidthPair = undefined; 356 var pixWidth = track.viewport_.xViewVectorToWorld(1); 357 358 // Small titles on big slices are not elided. 359 stringWidthPair = track.elidedTitleCache.get(track, pixWidth, smalltitle, 360 track.labelWidth(smalltitle), 1); 361 assertEquals(smalltitle, stringWidthPair.string); 362 // Keep shrinking the slice until eliding starts. 363 var elidedWhenSmallEnough = false; 364 for (var sliceLength = 1; sliceLength >= 0.00001; sliceLength /= 2.0) { 365 stringWidthPair = track.elidedTitleCache.get(track, pixWidth, smalltitle, 366 track.labelWidth(smalltitle), sliceLength); 367 if (stringWidthPair.string.length < smalltitle.length) { 368 elidedWhenSmallEnough = true; 369 break; 370 } 371 } 372 assertTrue(elidedWhenSmallEnough); 373 374 // Big titles are elided immediately. 375 var superBigTitle = ''; 376 for (var x = 0; x < 10; x++) { 377 superBigTitle += bigtitle; 378 } 379 stringWidthPair = track.elidedTitleCache.get(track, pixWidth, 380 superBigTitle, track.labelWidth(superBigTitle), 1); 381 assertTrue(stringWidthPair.string.length < superBigTitle.length); 382 // And elided text ends with ... 383 var len = stringWidthPair.string.length; 384 assertEquals('...', stringWidthPair.string.substring(len - 3, len)); 385 } 386 387 function testTimelineThreadTrackWithRegularSlices() { 388 var testEl = getTestDiv('testTimelineThreadTrackWithRegularSlices'); 389 var track = TimelineThreadTrack(); 390 testEl.appendChild(track); 391 track.heading = 'testTimelineThreadTrackWithRegularSlices'; 392 var thread = new TimelineThread(new TimelineProcess(7), 1); 393 thread.subRows = [ 394 [ 395 new TimelineThreadSlice('a', 0, 1, {}, 1), 396 new TimelineThreadSlice('b', 1, 2.1, {}, 4.8), 397 new TimelineThreadSlice('b', 1, 7, {}, 0.5), 398 new TimelineThreadSlice('c', 2, 7.6, {}, 0.4) 399 ], 400 [ 401 new TimelineThreadSlice('d', 3, 1.1, {}, 0.8), 402 new TimelineThreadSlice('e', 4, 7.1, {}, 0.3) 403 ] 404 ]; 405 thread.updateBounds(); 406 track.heading = 'thread regular'; 407 track.headingWidth = '150px'; 408 track.toolTip = thread.userFriendlyDetails + ':'; 409 track.thread = thread; 410 track.viewport = new TimelineViewport(testEl); 411 track.viewport.xSetWorldRange(0, 8.2, track.clientWidth); 412 } 413 414 function testTimelineThreadTrackWithTallSlices() { 415 var testEl = getTestDiv('testTimelineThreadTrackWithTallSlices'); 416 var track = TimelineThreadTrack(); 417 testEl.appendChild(track); 418 track.heading = 'testTimelineThreadTrackWithTallSlices'; 419 var thread = new TimelineThread(new TimelineProcess(7), 1); 420 thread.subRows = [ 421 [new TimelineThreadSlice('a', 1, 0, {}, 1)], 422 [new TimelineThreadSlice('b', 2, 0.1, {}, 0.8)], 423 [new TimelineThreadSlice('c', 3, 0.15, {}, 0.70)], 424 [new TimelineThreadSlice('d', 4, 0.20, {}, 0.50)], 425 [new TimelineThreadSlice('e', 5, 0.30, {}, 0.28)], 426 [new TimelineThreadSlice('e', 6, 0.35, {}, 0.20)], 427 [new TimelineThreadSlice('f', 7, 0.40, {}, 0.10)] 428 ]; 429 thread.updateBounds(); 430 track.heading = 'thread tall'; 431 track.headingWidth = '150px'; 432 track.toolTip = thread.userFriendlyDetails + ':'; 433 track.thread = thread; 434 track.viewport = new TimelineViewport(testEl); 435 track.viewport.xSetWorldRange(0, 1.1, track.clientWidth); 436 } 437 438 function testTimelineThreadTrackWithRegularAndAsyncSlices() { 439 var testEl = getTestDiv('testTimelineThreadTrackWithAsyncSlices'); 440 var track = TimelineThreadTrack(); 441 testEl.appendChild(track); 442 var thread = new TimelineThread(new TimelineProcess(7), 1); 443 thread.subRows = [ 444 [ 445 new TimelineThreadSlice('a', 0, 1, {}, 1), 446 new TimelineThreadSlice('b', 1, 2.1, {}, 4.8), 447 new TimelineThreadSlice('b', 1, 7, {}, 0.5), 448 new TimelineThreadSlice('c', 2, 7.6, {}, 0.4) 449 ], 450 [ 451 new TimelineThreadSlice('d', 3, 1.1, {}, 0.8), 452 new TimelineThreadSlice('e', 4, 7.1, {}, 0.3) 453 ] 454 ]; 455 thread.asyncSlices.push(newAsyncSlice(1.2, 7.2 - 1.2, thread, thread)); 456 thread.asyncSlices.push(newAsyncSlice(1.3, 7.3 - 1.3, thread, thread)); 457 thread.updateBounds(); 458 track.heading = 'thread regular + async'; 459 track.headingWidth = '150px'; 460 track.toolTip = thread.userFriendlyDetails + ':'; 461 track.thread = thread; 462 track.viewport = new TimelineViewport(testEl); 463 track.viewport.xSetWorldRange(0, 8.15, track.clientWidth); 464 } 465 466 function testTimelineSliceTrackAddItemNearToProvidedHit() { 467 var track = new TimelineSliceTrack(); 468 track.slices = [ 469 new TimelineSlice('a', 0, 1, {}, 1), 470 new TimelineSlice('b', 1, 2.1, {}, 4.8), 471 new TimelineSlice('b', 1, 7, {}, 0.5), 472 new TimelineSlice('c', 2, 7.6, {}, 0.4) 473 ]; 474 var sel = new tracing.TimelineSelection(); 475 track.addAllObjectsMatchingFilterToSelection(new tracing.TimelineFilter("b"), sel); 476 var ret; 477 478 // Select to the right of B. 479 var selRight = new tracing.TimelineSelection(); 480 ret = track.addItemNearToProvidedHitToSelection(sel[0], 1, selRight); 481 assertTrue(ret); 482 assertEquals(track.slices[2], selRight[0].slice); 483 484 // Select to the right of the 2nd b. 485 var selRight2 = new tracing.TimelineSelection(); 486 ret = track.addItemNearToProvidedHitToSelection(sel[0], 2, selRight2); 487 assertTrue(ret); 488 assertEquals(track.slices[3], selRight2[0].slice); 489 490 // Select to 2 to the right of the 2nd b. 491 var selRightOfRight = new tracing.TimelineSelection(); 492 ret = track.addItemNearToProvidedHitToSelection(selRight[0], 1, selRightOfRight); 493 assertTrue(ret); 494 assertEquals(track.slices[3], selRightOfRight[0].slice); 495 496 // Select to the right of the rightmost slice. 497 var selNone = new tracing.TimelineSelection(); 498 ret = track.addItemNearToProvidedHitToSelection(selRightOfRight[0], 1, selNone); 499 assertFalse(ret); 500 assertEquals(0, selNone.length); 501 502 // Select A and then select left. 503 var sel = new tracing.TimelineSelection(); 504 track.addAllObjectsMatchingFilterToSelection(new tracing.TimelineFilter("a"), sel); 505 var ret; 506 507 selNone = new tracing.TimelineSelection(); 508 ret = track.addItemNearToProvidedHitToSelection(sel[0], -1, selNone); 509 assertFalse(ret); 510 assertEquals(0, selNone.length); 511 512 } 513 </script> 514 </body> 515 </html> 516