<!DOCTYPE html>
<html>
<!--
Copyright (c) 2012 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<head>
<title>TraceEventImporter tests</title>
<script src="base.js"></script>
<script>
  base.require('unittest');
  base.require('test_utils');
  base.require('trace_event_importer');
</script>
</head>
<body>
<script>
'use strict';

var findSliceNamed = test_utils.findSliceNamed;

function testCanImportEmpty() {
  self.assertFalse(tracing.TraceEventImporter.canImport([]));
  self.assertFalse(tracing.TraceEventImporter.canImport(''));
}

function testBasicSingleThreadNonnestedParsing() {
  var events = [
    {name: 'a', args: {}, pid: 52, ts: 520, cat: 'foo', tid: 53, ph: 'B'},
    {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'},
    {name: 'b', args: {}, pid: 52, ts: 629, cat: 'bar', tid: 53, ph: 'B'},
    {name: 'b', args: {}, pid: 52, ts: 631, cat: 'bar', tid: 53, ph: 'E'}
  ];

  var m = new tracing.TimelineModel(events);
  assertEquals(1, m.numProcesses);
  var p = m.processes[52];
  assertNotUndefined(p);

  assertEquals(1, p.numThreads);
  var t = p.threads[53];
  assertNotUndefined(t);
  assertEquals(2, t.slices.length);
  assertEquals(53, t.tid);
  var slice = t.slices[0];
  assertEquals('a', slice.title);
  assertEquals('foo', slice.category);
  assertEquals(0, slice.start);
  assertAlmostEquals((560 - 520) / 1000, slice.duration);
  assertEquals(0, slice.subSlices.length);

  slice = t.slices[1];
  assertEquals('b', slice.title);
  assertEquals('bar', slice.category);
  assertAlmostEquals((629 - 520) / 1000, slice.start);
  assertAlmostEquals((631 - 629) / 1000, slice.duration);
  assertEquals(0, slice.subSlices.length);
}

function testArgumentDupeCreatesNonFailingImportError() {
  var events = [
    {name: 'a', args: {'x': 1}, pid: 1, ts: 520, cat: 'foo', tid: 1, ph: 'B'},
    {name: 'a', args: {'x': 2}, pid: 1, ts: 560, cat: 'foo', tid: 1, ph: 'E'}
  ];

  var m = new tracing.TimelineModel(events);
  var t = m.processes[1].threads[1];
  var sA = findSliceNamed(t.slices, 'a');

  assertEquals(2, sA.args.x);
  assertEquals(m.importErrors.length, 1);
}

function testCategoryBeginEndMismatchPrefersBegin() {
  var events = [
    {name: 'a', args: {}, pid: 52, ts: 520, cat: 'foo', tid: 53, ph: 'B'},
    {name: 'a', args: {}, pid: 52, ts: 560, cat: 'bar', tid: 53, ph: 'E'}
  ];

  var m = new tracing.TimelineModel(events);
  assertEquals(1, m.numProcesses);
  var p = m.processes[52];
  assertNotUndefined(p);

  assertEquals(1, p.numThreads);
  var t = p.threads[53];
  assertNotUndefined(t);
  assertEquals(1, t.slices.length);
  assertEquals(53, t.tid);
  var slice = t.slices[0];
  assertEquals('a', slice.title);
  assertEquals('foo', slice.category);
}

function testNestedParsing() {
  var events = [
    {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'},
    {name: 'b', args: {}, pid: 1, ts: 2, cat: 'bar', tid: 1, ph: 'B'},
    {name: 'b', args: {}, pid: 1, ts: 3, cat: 'bar', tid: 1, ph: 'E'},
    {name: 'a', args: {}, pid: 1, ts: 4, cat: 'foo', tid: 1, ph: 'E'}
  ];
  var m = new tracing.TimelineModel(events, false);
  var t = m.processes[1].threads[1];

  var sA = findSliceNamed(t.slices, 'a');
  var sB = findSliceNamed(t.slices, 'b');

  assertEquals('a', sA.title);
  assertEquals('foo', sA.category);
  assertEquals(0.001, sA.start);
  assertEquals(0.003, sA.duration);

  assertEquals('b', sB.title);
  assertEquals('bar', sB.category);
  assertEquals(0.002, sB.start);
  assertEquals(0.001, sB.duration);
}

function testAutoclosing() {
  var events = [
    // Slice that doesn't finish.
    {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'},

    // Slice that does finish to give an 'end time' to make autoclosing work.
    {name: 'b', args: {}, pid: 1, ts: 1, cat: 'bar', tid: 2, ph: 'B'},
    {name: 'b', args: {}, pid: 1, ts: 2, cat: 'bar', tid: 2, ph: 'E'}
  ];
  var m = new tracing.TimelineModel(events);
  var p = m.processes[1];
  var t = p.threads[1];
  var slice = t.slices[0];
  assertEquals('a', slice.title);
  assertEquals('foo', slice.category);
  assertTrue(slice.didNotFinish);
  assertEquals(0, slice.start);
  assertEquals((2 - 1) / 1000, slice.duration);
}

function testAutoclosingLoneBegin() {
  var events = [
    // Slice that doesn't finish.
    {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'}
  ];
  var m = new tracing.TimelineModel(events);
  var p = m.processes[1];
  var t = p.threads[1];
  var slice = t.slices[0];
  assertEquals('a', slice.title);
  assertEquals('foo', slice.category);
  assertTrue(slice.didNotFinish);
  assertEquals(0, slice.start);
  assertEquals(0, slice.duration);
}

function testAutoclosingWithSubTasks() {
  var events = [
    {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'},
    {name: 'b1', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 1, ph: 'B'},
    {name: 'b1', args: {}, pid: 1, ts: 3, cat: 'foo', tid: 1, ph: 'E'},
    {name: 'b2', args: {}, pid: 1, ts: 3, cat: 'foo', tid: 1, ph: 'B'}
  ];
  var m = new tracing.TimelineModel(events, false);
  var t = m.processes[1].threads[1];

  var sA = findSliceNamed(t.slices, 'a');
  var sB1 = findSliceNamed(t.slices, 'b1');
  var sB2 = findSliceNamed(t.slices, 'b2');

  assertEquals(0.003, sA.end);
  assertEquals(0.003, sB1.end);
  assertEquals(0.003, sB2.end);
}

function testAutoclosingWithEventsOutsideRange() {
  var events = [
    // Slice that begins before min and ends after max of the other threads.
    {name: 'a', args: {}, pid: 1, ts: 0, cat: 'foo', tid: 1, ph: 'B'},
    {name: 'b', args: {}, pid: 1, ts: 3, cat: 'foo', tid: 1, ph: 'B'},

    // Slice that does finish to give an 'end time' to establish a basis
    {name: 'c', args: {}, pid: 1, ts: 1, cat: 'bar', tid: 2, ph: 'B'},
    {name: 'c', args: {}, pid: 1, ts: 2, cat: 'bar', tid: 2, ph: 'E'}
  ];
  var m = new tracing.TimelineModel(events, false);
  var p = m.processes[1];
  var t = p.threads[1];
  assertEquals(2, t.slices.length);

  var slice = findSliceNamed(t.slices, 'a');
  assertEquals('a', slice.title);
  assertEquals('foo', slice.category);
  assertEquals(0, slice.start);
  assertEquals(0.003, slice.duration);

  var t2 = p.threads[2];
  var slice2 = findSliceNamed(t2.slices, 'c');
  assertEquals('c', slice2.title);
  assertEquals('bar', slice2.category);
  assertEquals(0.001, slice2.start);
  assertEquals(0.001, slice2.duration);

  assertEquals(0.000, m.minTimestamp);
  assertEquals(0.003, m.maxTimestamp);
}

function testNestedAutoclosing() {
  var events = [
    // Tasks that don't finish.
    {name: 'a1', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'},
    {name: 'a2', args: {}, pid: 1, ts: 1.5, cat: 'foo', tid: 1, ph: 'B'},

    // Slice that does finish to give an 'end time' to make autoclosing work.
    {name: 'b', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 2, ph: 'B'},
    {name: 'b', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 2, ph: 'E'}
  ];
  var m = new tracing.TimelineModel(events, false);
  var t1 = m.processes[1].threads[1];
  var t2 = m.processes[1].threads[2];

  var sA1 = findSliceNamed(t1.slices, 'a1');
  var sA2 = findSliceNamed(t1.slices, 'a2');
  var sB = findSliceNamed(t2.slices, 'b');

  assertEquals(0.002, sA1.end);
  assertEquals(0.002, sA2.end);
}

function testTaskColoring() {
  // The test below depends on hashing of 'a' != 'b'. Fail early if that
  // assumption is incorrect.
  assertNotEquals(tracing.getStringHash('a'), tracing.getStringHash('b'));

  var events = [
    {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'},
    {name: 'a', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 1, ph: 'E'},
    {name: 'b', args: {}, pid: 1, ts: 3, cat: 'bar', tid: 1, ph: 'B'},
    {name: 'b', args: {}, pid: 1, ts: 4, cat: 'bar', tid: 1, ph: 'E'},
    {name: 'a', args: {}, pid: 1, ts: 5, cat: 'baz', tid: 1, ph: 'B'},
    {name: 'a', args: {}, pid: 1, ts: 6, cat: 'baz', tid: 1, ph: 'E'}
  ];
  var m = new tracing.TimelineModel(events);
  var p = m.processes[1];
  var t = p.threads[1];
  var a1 = t.slices[0];
  assertEquals('a', a1.title);
  assertEquals('foo', a1.category);
  var b = t.slices[1];
  assertEquals('b', b.title);
  assertEquals('bar', b.category);
  assertNotEquals(a1.colorId, b.colorId);
  var a2 = t.slices[2];
  assertEquals('a', a2.title);
  assertEquals('baz', a2.category);
  assertEquals(a1.colorId, a2.colorId);
}

function testMultipleThreadParsing() {
  var events = [
    {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'},
    {name: 'a', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 1, ph: 'E'},
    {name: 'b', args: {}, pid: 1, ts: 3, cat: 'bar', tid: 2, ph: 'B'},
    {name: 'b', args: {}, pid: 1, ts: 4, cat: 'bar', tid: 2, ph: 'E'}
  ];
  var m = new tracing.TimelineModel(events);
  assertEquals(1, m.numProcesses);
  var p = m.processes[1];
  assertNotUndefined(p);

  assertEquals(2, p.numThreads);

  // Check thread 1.
  var t = p.threads[1];
  assertNotUndefined(t);
  assertEquals(1, t.slices.length);
  assertEquals(1, t.tid);

  var slice = t.slices[0];
  assertEquals('a', slice.title);
  assertEquals('foo', slice.category);
  assertEquals(0, slice.start);
  assertEquals((2 - 1) / 1000, slice.duration);
  assertEquals(0, slice.subSlices.length);

  // Check thread 2.
  var t = p.threads[2];
  assertNotUndefined(t);
  assertEquals(1, t.slices.length);
  assertEquals(2, t.tid);

  slice = t.slices[0];
  assertEquals('b', slice.title);
  assertEquals('bar', slice.category);
  assertEquals((3 - 1) / 1000, slice.start);
  assertEquals((4 - 3) / 1000, slice.duration);
  assertEquals(0, slice.subSlices.length);
}

function testMultiplePidParsing() {
  var events = [
    {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'},
    {name: 'a', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 1, ph: 'E'},
    {name: 'b', args: {}, pid: 2, ts: 3, cat: 'bar', tid: 2, ph: 'B'},
    {name: 'b', args: {}, pid: 2, ts: 4, cat: 'bar', tid: 2, ph: 'E'}
  ];
  var m = new tracing.TimelineModel(events);
  assertEquals(2, m.numProcesses);
  var p = m.processes[1];
  assertNotUndefined(p);

  assertEquals(1, p.numThreads);

  // Check process 1 thread 1.
  var t = p.threads[1];
  assertNotUndefined(t);
  assertEquals(1, t.slices.length);
  assertEquals(1, t.tid);

  var slice = t.slices[0];
  assertEquals('a', slice.title);
  assertEquals('foo', slice.category);
  assertEquals(0, slice.start);
  assertEquals((2 - 1) / 1000, slice.duration);
  assertEquals(0, slice.subSlices.length);

  // Check process 2 thread 2.
  var p = m.processes[2];
  assertNotUndefined(p);
  assertEquals(1, p.numThreads);
  var t = p.threads[2];
  assertNotUndefined(t);
  assertEquals(1, t.slices.length);
  assertEquals(2, t.tid);

  slice = t.slices[0];
  assertEquals('b', slice.title);
  assertEquals('bar', slice.category);
  assertEquals((3 - 1) / 1000, slice.start);
  assertEquals((4 - 3) / 1000, slice.duration);
  assertEquals(0, slice.subSlices.length);

  // Check getAllThreads.
  assertArrayEquals([m.processes[1].threads[1], m.processes[2].threads[2]],
                    m.getAllThreads());
}

// Thread names.
function testThreadNames() {
  var events = [
    {name: 'thread_name', args: {name: 'Thread 1'},
      pid: 1, ts: 0, tid: 1, ph: 'M'},
    {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'},
    {name: 'a', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 1, ph: 'E'},
    {name: 'b', args: {}, pid: 2, ts: 3, cat: 'foo', tid: 2, ph: 'B'},
    {name: 'b', args: {}, pid: 2, ts: 4, cat: 'foo', tid: 2, ph: 'E'},
    {name: 'thread_name', args: {name: 'Thread 2'},
      pid: 2, ts: 0, tid: 2, ph: 'M'}
  ];
  var m = new tracing.TimelineModel(events);
  assertEquals('Thread 1', m.processes[1].threads[1].name);
  assertEquals('Thread 2', m.processes[2].threads[2].name);
}

function testParsingWhenEndComesFirst() {
  var events = [
    {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'E'},
    {name: 'a', args: {}, pid: 1, ts: 4, cat: 'foo', tid: 1, ph: 'B'},
    {name: 'a', args: {}, pid: 1, ts: 5, cat: 'foo', tid: 1, ph: 'E'}
  ];
  var m = new tracing.TimelineModel(events, false);
  var p = m.processes[1];
  var t = p.threads[1];
  assertEquals(1, t.slices.length);
  assertEquals('a', t.slices[0].title);
  assertEquals('foo', t.slices[0].category);
  assertEquals(0.004, t.slices[0].start);
  assertEquals(0.001, t.slices[0].duration);
  assertEquals(1, m.importErrors.length);
}

function testImmediateParsing() {
  var events = [
    // Need to include immediates inside a task so the timeline
    // recentering/zeroing doesn't clobber their timestamp.
    {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'},
    {name: 'immediate', args: {}, pid: 1, ts: 2, cat: 'bar', tid: 1, ph: 'I'},
    {name: 'a', args: {}, pid: 1, ts: 4, cat: 'foo', tid: 1, ph: 'E'}
  ];
  var m = new tracing.TimelineModel(events, false);
  var p = m.processes[1];
  var t = p.threads[1];
  assertEquals(2, t.slices.length);
  assertEquals(0.002, t.slices[0].start);
  assertEquals(0, t.slices[0].duration);
  assertEquals(0.001, t.slices[1].start);
  assertEquals(0.003, t.slices[1].duration);

  assertEquals(2, t.slices.length);
  var slice = findSliceNamed(t.slices, 'a');
  assertEquals('a', slice.title);
  assertEquals('foo', slice.category);
  assertEquals(0.003, slice.duration);

  var immed = findSliceNamed(t.slices, 'immediate');
  assertEquals('immediate', immed.title);
  assertEquals('bar', immed.category);
  assertEquals(0.002, immed.start);
  assertEquals(0, immed.duration);
  assertEquals(0, immed.subSlices.length);
}

function testSimpleCounter() {
  var events = [
    {name: 'ctr', args: {'value': 0}, pid: 1, ts: 0, cat: 'foo', tid: 1,
     ph: 'C'},
    {name: 'ctr', args: {'value': 10}, pid: 1, ts: 10, cat: 'foo', tid: 1,
     ph: 'C'},
    {name: 'ctr', args: {'value': 0}, pid: 1, ts: 20, cat: 'foo', tid: 1,
     ph: 'C'}

  ];
  var m = new tracing.TimelineModel(events);
  var p = m.processes[1];
  var ctr = m.processes[1].counters['foo.ctr'];

  assertEquals('ctr', ctr.name);
  assertEquals('foo', ctr.category);
  assertEquals(3, ctr.numSamples);
  assertEquals(1, ctr.numSeries);

  assertArrayEquals(['value'], ctr.seriesNames);
  assertArrayEquals([tracing.getStringColorId('ctr.value')], ctr.seriesColors);
  assertArrayEquals([0, 0.01, 0.02], ctr.timestamps);
  assertArrayEquals([0, 10, 0], ctr.samples);
  assertArrayEquals([0, 10, 0], ctr.totals);
  assertEquals(10, ctr.maxTotal);
}

function testInstanceCounter() {
  var events = [
    {name: 'ctr', args: {'value': 0}, pid: 1, ts: 0, cat: 'foo', tid: 1,
     ph: 'C', id: 0},
    {name: 'ctr', args: {'value': 10}, pid: 1, ts: 10, cat: 'foo', tid: 1,
     ph: 'C', id: 0},
    {name: 'ctr', args: {'value': 10}, pid: 1, ts: 10, cat: 'foo', tid: 1,
     ph: 'C', id: 1},
    {name: 'ctr', args: {'value': 20}, pid: 1, ts: 15, cat: 'foo', tid: 1,
     ph: 'C', id: 1},
    {name: 'ctr', args: {'value': 30}, pid: 1, ts: 18, cat: 'foo', tid: 1,
     ph: 'C', id: 1},
    {name: 'ctr', args: {'value': 40}, pid: 1, ts: 20, cat: 'bar', tid: 1,
     ph: 'C', id: 2}
  ];
  var m = new tracing.TimelineModel(events);
  var p = m.processes[1];
  var ctr = m.processes[1].counters['foo.ctr[0]'];
  assertEquals('ctr[0]', ctr.name);
  assertEquals('foo', ctr.category);
  assertEquals(2, ctr.numSamples);
  assertEquals(1, ctr.numSeries);
  assertArrayEquals([0, 0.01], ctr.timestamps);
  assertArrayEquals([0, 10], ctr.samples);

  var ctr = m.processes[1].counters['foo.ctr[1]'];
  assertEquals('ctr[1]', ctr.name);
  assertEquals('foo', ctr.category);
  assertEquals(3, ctr.numSamples);
  assertEquals(1, ctr.numSeries);
  assertArrayEquals([0.01, 0.015, 0.018], ctr.timestamps);
  assertArrayEquals([10, 20, 30], ctr.samples);

  var ctr = m.processes[1].counters['bar.ctr[2]'];
  assertEquals('ctr[2]', ctr.name);
  assertEquals('bar', ctr.category);
  assertEquals(1, ctr.numSamples);
  assertEquals(1, ctr.numSeries);
  assertArrayEquals([0.02], ctr.timestamps);
  assertArrayEquals([40], ctr.samples);
}

function testMultiCounterUpdateBounds() {
  var ctr = new tracing.TimelineCounter(undefined, 'testBasicCounter',
      '', 'testBasicCounter');
  ctr.seriesNames = ['value1', 'value2'];
  ctr.seriesColors = ['testBasicCounter.value1', 'testBasicCounter.value2'];
  ctr.timestamps = [0, 1, 2, 3, 4, 5, 6, 7];
  ctr.samples = [0, 0,
                 1, 0,
                 1, 1,
                 2, 1.1,
                 3, 0,
                 1, 7,
                 3, 0,
                 3.1, 0.5];
  ctr.updateBounds();
  assertEquals(0, ctr.minTimestamp);
  assertEquals(7, ctr.maxTimestamp);
  assertEquals(8, ctr.maxTotal);
  assertArrayEquals([0, 0,
                     1, 1,
                     1, 2,
                     2, 3.1,
                     3, 3,
                     1, 8,
                     3, 3,
                     3.1, 3.6], ctr.totals);
}

function testMultiCounter() {
  var events = [
    {name: 'ctr', args: {'value1': 0, 'value2': 7}, pid: 1, ts: 0, cat: 'foo',
     tid: 1, ph: 'C'},
    {name: 'ctr', args: {'value1': 10, 'value2': 4}, pid: 1, ts: 10, cat: 'foo',
     tid: 1, ph: 'C'},
    {name: 'ctr', args: {'value1': 0, 'value2': 1 }, pid: 1, ts: 20, cat: 'foo',
     tid: 1, ph: 'C'}
  ];
  var m = new tracing.TimelineModel(events);
  var p = m.processes[1];
  var ctr = m.processes[1].counters['foo.ctr'];
  assertEquals('ctr', ctr.name);

  assertEquals('ctr', ctr.name);
  assertEquals('foo', ctr.category);
  assertEquals(3, ctr.numSamples);
  assertEquals(2, ctr.numSeries);

  assertArrayEquals(['value1', 'value2'], ctr.seriesNames);
  assertArrayEquals([tracing.getStringColorId('ctr.value1'),
                     tracing.getStringColorId('ctr.value2')],
                    ctr.seriesColors);
  assertArrayEquals([0, 0.01, 0.02], ctr.timestamps);
  assertArrayEquals([0, 7,
                     10, 4,
                     0, 1], ctr.samples);
  assertArrayEquals([0, 7,
                     10, 14,
                     0, 1], ctr.totals);
  assertEquals(14, ctr.maxTotal);
}

function testImportObjectInsteadOfArray() {
  var events = { traceEvents: [
    {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53, ph: 'B'},
    {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'}
  ] };

  var m = new tracing.TimelineModel(events);
  assertEquals(1, m.numProcesses);
}

function testImportString() {
  var events = [
    {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53, ph: 'B'},
    {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'}
  ];

  var m = new tracing.TimelineModel(JSON.stringify(events));
  assertEquals(1, m.numProcesses);
}

function testImportStringWithTrailingNewLine() {
  var events = [
    {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53, ph: 'B'},
    {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'}
  ];

  var m = new tracing.TimelineModel(JSON.stringify(events) + '\n');
  assertEquals(1, m.numProcesses);
}

function testImportStringWithMissingCloseSquareBracket() {
  var events = [
    {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53, ph: 'B'},
    {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'}
  ];

  var tmp = JSON.stringify(events);
  assertEquals(']', tmp[tmp.length - 1]);

  // Drop off the trailing ]
  var dropped = tmp.substring(0, tmp.length - 1);
  var m = new tracing.TimelineModel(dropped);
  assertEquals(1, m.numProcesses);
}

function testImportStringWithEndingCommaButMissingCloseSquareBracket() {
  var lines = [
    '[',
    '{"name": "a", "args": {}, "pid": 52, "ts": 524, "cat": "foo", "tid": 53, "ph": "B"},',
    '{"name": "a", "args": {}, "pid": 52, "ts": 560, "cat": "foo", "tid": 53, "ph": "E"},'
    ]
  var text = lines.join('\n');

  var m = new tracing.TimelineModel(text);
  assertEquals(1, m.numProcesses);
  assertEquals(1, m.processes[52].threads[53].slices.length);
}

function testImportStringWithMissingCloseSquareBracketAndNewline() {
  var events = [
    {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53, ph: 'B'},
    {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'}
  ];

  var tmp = JSON.stringify(events);
  assertEquals(']', tmp[tmp.length - 1]);

  // Drop off the trailing ] and add a newline
  var dropped = tmp.substring(0, tmp.length - 1);
  var m = new tracing.TimelineModel(dropped + '\n');
  assertEquals(1, m.numProcesses);
}

function testImportStringWithEndingCommaButMissingCloseSquareBracketCRLF() {
  var lines = [
    '[',
    '{"name": "a", "args": {}, "pid": 52, "ts": 524, "cat": "foo", "tid": 53, "ph": "B"},',
    '{"name": "a", "args": {}, "pid": 52, "ts": 560, "cat": "foo", "tid": 53, "ph": "E"},'
    ]
  var text = lines.join('\r\n');

  var m = new tracing.TimelineModel(text);
  assertEquals(1, m.numProcesses);
  assertEquals(1, m.processes[52].threads[53].slices.length);
}

function testStartFinishOneSliceOneThread() {
  var events = [
    // Time is intentionally out of order.
    {name: 'a', args: {}, pid: 52, ts: 560, cat: 'cat', tid: 53,
       ph: 'F', id: 72},
    {name: 'a', pid: 52, ts: 524, cat: 'cat', tid: 53,
       ph: 'S', id: 72, args: {'foo': 'bar'}}
  ];

  var m = new tracing.TimelineModel(events);
  var t = m.processes[52].threads[53];
  assertNotUndefined(t);
  assertEquals(1, t.asyncSlices.slices.length);
  assertEquals('a', t.asyncSlices.slices[0].title);
  assertEquals('cat', t.asyncSlices.slices[0].category);
  assertEquals(72, t.asyncSlices.slices[0].id);
  assertEquals('bar', t.asyncSlices.slices[0].args.foo);
  assertEquals(0, t.asyncSlices.slices[0].start);
  assertAlmostEquals((60 - 24) / 1000, t.asyncSlices.slices[0].duration);
  assertEquals(t, t.asyncSlices.slices[0].startThread);
  assertEquals(t, t.asyncSlices.slices[0].endThread);
}

function testEndArgsAddedToSlice() {
  var events = [
    {name: 'a', args: {x: 1}, pid: 52, ts: 520, cat: 'foo', tid: 53, ph: 'B'},
    {name: 'a', args: {y: 2}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'}
  ];

  var m = new tracing.TimelineModel(events);
  assertEquals(1, m.numProcesses);
  var p = m.processes[52];
  assertNotUndefined(p);

  assertEquals(1, p.numThreads);
  var t = p.threads[53];
  assertNotUndefined(t);
  assertEquals(1, t.slices.length);
  assertEquals(53, t.tid);
  var slice = t.slices[0];
  assertEquals('a', slice.title);
  assertEquals('foo', slice.category);
  assertEquals(0, slice.start);
  assertEquals(0, slice.subSlices.length);
  assertEquals(1, slice.args['x']);
  assertEquals(2, slice.args['y']);
}

function testEndArgOverrwritesOriginalArgValueIfDuplicated() {
  var events = [
    {name: 'b', args: {z: 3}, pid: 52, ts: 629, cat: 'foo', tid: 53, ph: 'B'},
    {name: 'b', args: {z: 4}, pid: 52, ts: 631, cat: 'foo', tid: 53, ph: 'E'}
  ];

  var m = new tracing.TimelineModel(events);
  assertEquals(1, m.numProcesses);
  var p = m.processes[52];
  assertNotUndefined(p);

  assertEquals(1, p.numThreads);
  var t = p.threads[53];
  assertNotUndefined(t);
  var slice = t.slices[0];
  assertEquals('b', slice.title);
  assertEquals('foo', slice.category);
  assertEquals(0, slice.start);
  assertEquals(0, slice.subSlices.length);
  assertEquals(4, slice.args['z']);
}

function testAsyncEndArgsAddedToSlice() {
  var events = [
    // Time is intentionally out of order.
    {name: 'c', args: {y: 2}, pid: 52, ts: 560, cat: 'foo', tid: 53,
       ph: 'F', id: 72},
    {name: 'c', args: {x: 1}, pid: 52, ts: 524, cat: 'foo', tid: 53,
       ph: 'S', id: 72}
  ];

  var m = new tracing.TimelineModel(events);
  var t = m.processes[52].threads[53];
  assertNotUndefined(t);
  assertEquals(1, t.asyncSlices.slices.length);
  var parentSlice = t.asyncSlices.slices[0];
  assertEquals('c', parentSlice.title);
  assertEquals('foo', parentSlice.category);

  assertNotUndefined(parentSlice.subSlices);
  assertEquals(1, parentSlice.subSlices.length);
  var subSlice = parentSlice.subSlices[0];
  assertEquals(1, subSlice.args['x']);
  assertEquals(2, subSlice.args['y']);
}

function testAsyncEndArgOverrwritesOriginalArgValueIfDuplicated() {
  var events = [
    // Time is intentionally out of order.
    {name: 'd', args: {z: 4}, pid: 52, ts: 560, cat: 'foo', tid: 53,
       ph: 'F', id: 72},
    {name: 'd', args: {z: 3}, pid: 52, ts: 524, cat: 'foo', tid: 53,
       ph: 'S', id: 72}
  ];

  var m = new tracing.TimelineModel(events);
  var t = m.processes[52].threads[53];
  assertNotUndefined(t);
  assertEquals(1, t.asyncSlices.slices.length);
  var parentSlice = t.asyncSlices.slices[0];
  assertEquals('d', parentSlice.title);
  assertEquals('foo', parentSlice.category);

  assertNotUndefined(parentSlice.subSlices);
  assertEquals(1, parentSlice.subSlices.length);
  var subSlice = parentSlice.subSlices[0];
  assertEquals(4, subSlice.args['z']);
}

function testAsyncStepsInOneThread() {
  var events = [
    // Time is intentionally out of order.
    {name: 'a', args: {z: 3}, pid: 52, ts: 560, cat: 'foo', tid: 53,
       ph: 'F', id: 72},
    {name: 'a', args: {step: 's1', y: 2}, pid: 52, ts: 548, cat: 'foo', tid: 53,
       ph: 'T', id: 72},
    {name: 'a', args: {x: 1}, pid: 52, ts: 524, cat: 'foo', tid: 53,
       ph: 'S', id: 72}
  ];

  var m = new tracing.TimelineModel(events);
  var t = m.processes[52].threads[53];
  assertNotUndefined(t);
  assertEquals(1, t.asyncSlices.slices.length);
  var parentSlice = t.asyncSlices.slices[0];
  assertEquals('a', parentSlice.title);
  assertEquals('foo', parentSlice.category);
  assertEquals(0, parentSlice.start);

  assertNotUndefined(parentSlice.subSlices);
  assertEquals(2, parentSlice.subSlices.length);
  var subSlice = parentSlice.subSlices[0];
  assertEquals('a', subSlice.title);
  assertEquals('foo', subSlice.category);
  assertEquals(0, subSlice.start);
  assertAlmostEquals((548 - 524) / 1000, subSlice.duration);
  assertEquals(1, subSlice.args['x']);

  var subSlice = parentSlice.subSlices[1];
  assertEquals('a:s1', subSlice.title);
  assertEquals('foo', subSlice.category);
  assertAlmostEquals((548 - 524) / 1000, subSlice.start);
  assertAlmostEquals((560 - 548) / 1000, subSlice.duration);
  assertEquals(2, subSlice.args['y']);
  assertEquals(3, subSlice.args['z']);
}

function testAsyncStepsMissingStart() {
  var events = [
    // Time is intentionally out of order.
    {name: 'a', args: {z: 3}, pid: 52, ts: 560, cat: 'foo', tid: 53,
       ph: 'F', id: 72},
    {name: 'a', args: {step: 's1', y: 2}, pid: 52, ts: 548, cat: 'foo', tid: 53,
       ph: 'T', id: 72}
  ];

  var m = new tracing.TimelineModel(events);
  var t = m.processes[52].threads[53];
  assertUndefined(t);
}

function testAsyncStepsMissingFinish() {
  var events = [
    // Time is intentionally out of order.
    {name: 'a', args: {step: 's1', y: 2}, pid: 52, ts: 548, cat: 'foo', tid: 53,
       ph: 'T', id: 72},
    {name: 'a', args: {z: 3}, pid: 52, ts: 560, cat: 'foo', tid: 53,
       ph: 'S', id: 72}
  ];

  var m = new tracing.TimelineModel(events);
  var t = m.processes[52].threads[53];
  assertUndefined(t);
}

// TODO(nduca): one slice, two threads
// TODO(nduca): one slice, two pids
</script>
</body>
</html>