1<!DOCTYPE html>
2<!--
3Copyright (c) 2012 The Chromium Authors. All rights reserved.
4Use of this source code is governed by a BSD-style license that can be
5found in the LICENSE file.
6-->
7
8<link rel="import" href="/tracing/base/iteration_helpers.html">
9<link rel="import" href="/tracing/importer/import.html">
10<link rel="import" href="/tracing/model/counter.html">
11<link rel="import" href="/tracing/model/model.html">
12<link rel="import" href="/tracing/model/slice.html">
13<link rel="import" href="/tracing/model/slice_group.html">
14<link rel="import" href="/tracing/model/stack_frame.html">
15<link rel="import" href="/tracing/model/thread_time_slice.html">
16<link rel="import" href="/tracing/model/user_model/stub_expectation.html">
17
18<script>
19'use strict';
20
21/**
22 * @fileoverview Helper functions for use in tracing tests.
23 */
24tr.exportTo('tr.c', function() {
25  var ColorScheme = tr.b.ColorScheme;
26
27  function _getStartAndCpuDurationFromDict(
28      options, required, startFieldName, durationFieldName, endFieldName) {
29
30    if (options[startFieldName] === undefined) {
31      if (required)
32        throw new Error('Too little information.');
33      else
34        return {start: undefined, duration: undefined};
35    }
36    if (options[durationFieldName] !== undefined &&
37        options[endFieldName] !== undefined) {
38      throw new Error('Too much information.');
39    }
40    if (options[durationFieldName] === undefined &&
41        options[endFieldName] === undefined) {
42      if (required)
43        throw new Error('Too little information.');
44      else
45        return {start: undefined, duration: undefined};
46    }
47
48    var duration;
49    if (options[durationFieldName] !== undefined) {
50      duration = options[durationFieldName];
51    } else {
52      duration = options[endFieldName] - options[startFieldName];
53    }
54
55    return {
56      start: options[startFieldName],
57      duration: duration
58    };
59  }
60
61  function _maybeGetCpuStartAndCpuDurationFromDict(options) {
62    return _getStartAndCpuDurationFromDict(
63        options, false, 'cpuStart', 'cpuDuration', 'cpuEnd');
64  }
65
66  function TestUtils() {
67  }
68
69  TestUtils.getStartAndDurationFromDict = function(options) {
70    return _getStartAndCpuDurationFromDict(
71        options, true, 'start', 'duration', 'end');
72  };
73
74  TestUtils.newAsyncSlice = function(start, duration, startThread, endThread) {
75    return TestUtils.newAsyncSliceNamed(
76        'a', start, duration, startThread, endThread);
77  };
78
79  TestUtils.newAsyncSliceNamed = function(
80      name, start, duration, startThread, endThread) {
81    var asyncSliceConstructor =
82        tr.model.AsyncSlice.getConstructor('', name);
83
84    var s = new asyncSliceConstructor('', name, 0, start);
85    s.duration = duration;
86    s.startThread = startThread;
87    s.endThread = endThread;
88    return s;
89  };
90
91  function getColorId(colorId) {
92    if (colorId) {
93      if (colorId === 'random') {
94        colorId = Math.floor(
95            Math.random() *
96            ColorScheme.proprties.numGeneralPurposeColorIds);
97      }
98    } else {
99      colorId = 0;
100    }
101    return colorId;
102  }
103
104  TestUtils.newAsyncSliceEx = function(options) {
105    var sd = TestUtils.getStartAndDurationFromDict(options);
106
107    var cat = options.cat ? options.cat : 'cat';
108    var title = options.title ? options.title : 'a';
109    var colorId = getColorId(options.colorId);
110
111    var isTopLevel;
112    if (options.isTopLevel !== undefined)
113      isTopLevel = options.isTopLevel;
114    else
115      isTopLevel = false;
116
117    var asyncSliceConstructor =
118        tr.model.AsyncSlice.getConstructor(cat, title);
119
120    var slice = new asyncSliceConstructor(
121        cat,
122        title,
123        colorId,
124        sd.start,
125        options.args ? options.args : {},
126        sd.duration, isTopLevel);
127
128    if (options.id)
129      slice.id = options.id;
130    else
131      slice.id = tr.b.GUID.allocate();
132
133    if (options.startStackFrame)
134      slice.startStackFrame = options.startStackFrame;
135    if (options.endStackFrame)
136      slice.endStackFrame = options.endStackFrame;
137    if (options.important)
138      slice.important = options.important;
139    if (options.startThread)
140      slice.startThread = options.startThread;
141    if (options.endThread)
142      slice.endThread = options.endThread;
143    return slice;
144  };
145
146  TestUtils.newCounter = function(parent) {
147    return TestUtils.newCounterNamed(parent, 'a');
148  };
149
150  TestUtils.newCounterNamed = function(parent, name) {
151    var s = new tr.model.Counter(parent, name, null, name);
152    return s;
153  };
154
155  TestUtils.newCounterCategory = function(parent, category, name) {
156    var s = new tr.model.Counter(parent, name, category, name);
157    return s;
158  };
159
160  TestUtils.newCounterSeries = function() {
161    var s = new tr.model.CounterSeries('a', 0);
162    return s;
163  };
164
165  TestUtils.newFlowEventEx = function(options) {
166    if (options.start === undefined)
167      throw new Error('Too little info');
168
169    var title = options.title ? options.title : 'a';
170
171    var colorId = options.colorId ? options.colorId : 0;
172
173    var sd = TestUtils.getStartAndDurationFromDict(options);
174
175    var id;
176    if (options.id !== undefined)
177      id = options.id;
178    else
179      id = tr.b.GUID.allocate();
180
181    var event = new tr.model.FlowEvent(
182        options.cat ? options.cat : 'cat',
183        id,
184        title,
185        colorId,
186        sd.start,
187        options.args ? options.args : {},
188        sd.duration);
189
190    if (options.startStackFrame)
191      event.startStackFrame = options.startStackFrame;
192    if (options.endStackFrame)
193      event.endStackFrame = options.endStackFrame;
194    if (options.important)
195      event.important = options.important;
196    if (options.startSlice) {
197      event.startSlice = options.startSlice;
198      event.startSlice.outFlowEvents.push(event);
199    }
200    if (options.endSlice) {
201      event.endSlice = options.endSlice;
202      event.endSlice.inFlowEvents.push(event);
203    }
204    return event;
205  };
206
207  TestUtils.newThreadSlice = function(thread, state, start, duration, opt_cpu) {
208    var s = new tr.model.ThreadTimeSlice(
209        thread, state, 'cat', start, {}, duration);
210    if (opt_cpu)
211      s.cpuOnWhichThreadWasRunning = opt_cpu;
212    return s;
213  };
214
215  TestUtils.newSampleNamed = function(
216      thread, sampleName, category, frameNames, start) {
217    var model;
218    if (thread.parent)
219      model = thread.parent.model;
220    else
221      model = undefined;
222    var sf = TestUtils.newStackTrace(model, frameNames);
223    var s = new tr.model.Sample(undefined, thread,
224                                        sampleName, start,
225                                        sf,
226                                        1);
227    return s;
228  };
229
230  TestUtils.newSliceEx = function(options) {
231    var sd = TestUtils.getStartAndDurationFromDict(options);
232
233    var title = options.title ? options.title : 'a';
234
235    var colorId = options.colorId ? options.colorId : 0;
236
237    var cpuSD = _maybeGetCpuStartAndCpuDurationFromDict(options);
238
239    var type;
240    if (options.type)
241      type = options.type;
242    else
243      type = tr.model.Slice;
244
245    var slice = new type(
246        options.cat ? options.cat : 'cat',
247        title,
248        colorId,
249        sd.start,
250        options.args ? options.args : {},
251        sd.duration,
252        cpuSD.start, cpuSD.duration);
253
254
255    return slice;
256  };
257
258  TestUtils.newStackTrace = function(model, titles) {
259    var frame = undefined;
260    titles.forEach(function(title) {
261      frame = new tr.model.StackFrame(frame, tr.b.GUID.allocate(), title, 7);
262      if (model)
263        model.addStackFrame(frame);
264    });
265    return frame;
266  };
267
268  TestUtils.findSliceNamed = function(slices, name) {
269    if (slices instanceof tr.model.SliceGroup)
270      slices = slices.slices;
271    for (var i = 0; i < slices.length; i++)
272      if (slices[i].title == name)
273        return slices[i];
274      return undefined;
275  };
276
277  TestUtils.newInteractionRecord = function(parentModel, start, duration) {
278    return new tr.model.um.StubExpectation({
279      parentModel: parentModel, start: start, duration: duration});
280  };
281
282  TestUtils.newModel = function(customizeModelCallback) {
283    return TestUtils.newModelWithEvents([], {
284      shiftWorldToZero: false,
285      pruneEmptyContainers: false,
286      customizeModelCallback: customizeModelCallback
287    });
288  };
289
290  TestUtils.newModelWithEvents = function(events, opts) {
291    if (!(events instanceof Array))
292      events = [events];
293
294    opts = opts || {};
295
296    var io = new tr.importer.ImportOptions();
297    io.showImportWarnings = false;
298    io.customizeModelCallback = opts.customizeModelCallback;
299    io.trackDetailedModelStats = opts.trackDetailedModelStats === undefined ?
300        false : opts.trackDetailedModelStats;
301    io.shiftWorldToZero = opts.shiftWorldToZero === undefined ?
302        true : opts.shiftWorldToZero;
303    io.pruneEmptyContainers = opts.pruneEmptyContainers === undefined ?
304        true : opts.pruneEmptyContainers;
305    io.auditorConstructors = opts.auditorConstructors === undefined ?
306        [] : opts.auditorConstructors;
307
308    var m = new tr.Model();
309    var i = new tr.importer.Import(m, io);
310    i.importTraces(events);
311    return m;
312  };
313
314  TestUtils.newModelWithAuditor = function(customizeModelCallback, auditor) {
315    return TestUtils.newModelWithEvents([], {
316      shiftWorldToZero: false,
317      pruneEmptyContainers: false,
318      customizeModelCallback: customizeModelCallback,
319      auditorConstructors: [auditor]
320    });
321  };
322
323  TestUtils.newFakeThread = function() {
324    var process = {model: {}};
325    return new tr.model.Thread(process);
326  };
327
328  /** @constructor */
329  TestUtils.SourceGenerator = function() {
330    this.sourceList_ = [];
331    this.currentLineCommentList_ = [];
332    this.currentIndent_ = 0;
333    this.currentLineEmpty_ = true;
334  };
335
336  TestUtils.SourceGenerator.prototype = {
337    push: function(/* arguments */) {
338      if (this.currentLineEmpty_) {
339        this.sourceList_.push(' '.repeat(this.currentIndent_));
340        this.currentLineEmpty_ = false;
341      }
342      this.sourceList_.push.apply(
343          this.sourceList_, Array.prototype.slice.call(arguments));
344    },
345
346    pushComment: function(/* arguments */) {
347      this.currentLineCommentList_.push.apply(
348          this.currentLineCommentList_, Array.prototype.slice.call(arguments));
349    },
350
351    build: function() {
352      this.finishLine_();
353      return this.sourceList_.join('');
354    },
355
356    breakLine: function() {
357      this.finishLine_();
358      this.push('\n');
359      this.currentLineEmpty_ = true;
360    },
361
362    finishLine_: function() {
363      if (this.currentLineCommentList_.length === 0)
364        return;
365      this.push('  // ');
366      this.push.apply(this, this.currentLineCommentList_);
367      this.push('.');
368      this.currentLineCommentList_ = [];
369    },
370
371    indentBlock: function(spaces, breakLine, blockCallback, opt_this) {
372      opt_this = opt_this || this;
373      this.currentIndent_ += spaces;
374      if (breakLine)
375        this.breakLine();
376      blockCallback.call(opt_this);
377      this.currentIndent_ -= spaces;
378    },
379
380    formatSingleLineList: function(list, itemCallback, opt_this) {
381      opt_this = opt_this || this;
382      this.push('[');
383      tr.b.asArray(list).forEach(function(item, index) {
384        if (index > 0)
385          this.push(', ');
386        itemCallback.call(opt_this, item, index);
387      }, this);
388      this.push(']');
389    },
390
391    formatMultiLineList: function(list, itemCallback, opt_this) {
392      opt_this = opt_this || this;
393      this.push('[');
394      this.indentBlock(2, false /* don't break line */, function() {
395        tr.b.asArray(list).forEach(function(item, index) {
396          if (index > 0)
397            this.push(',');
398          this.breakLine();
399          itemCallback.call(opt_this, item, index);
400        }, this);
401      }, this);
402      if (list.length > 0)
403        this.breakLine();
404      this.push(']');
405    },
406
407    formatString: function(string) {
408      if (string === undefined)
409        this.push('undefined');
410      else
411        this.push('\'', string, '\'');
412    }
413  };
414
415  TestUtils.addSourceListing = function(test, source) {
416    var testSourceEl = document.createElement('pre');
417    testSourceEl.style.fontFamily = 'monospace';
418    testSourceEl.textContent = source;
419
420    var copyButtonEl = document.createElement('button');
421    copyButtonEl.textContent = 'Copy into to clipboard';
422    copyButtonEl.addEventListener('click', function() {
423      var selection = window.getSelection();
424
425      // Store the original selection.
426      var originalRanges = new Array(selection.rangeCount);
427      for (var i = 0; i < originalRanges.length; i++)
428        originalRanges[i] = selection.getRangeAt(i);
429
430      // Copy the generated test source code into clipboard.
431      selection.removeAllRanges();
432      var range = document.createRange();
433      range.selectNode(testSourceEl);
434      selection.addRange(range);
435      document.execCommand('copy');
436
437      // Restore the original selection.
438      selection.removeAllRanges();
439      for (var i = 0; i < originalRanges.length; i++)
440        selection.addRange(originalRanges[i]);
441    });
442
443    var outputEl = document.createElement('div');
444    outputEl.appendChild(copyButtonEl);
445    outputEl.appendChild(testSourceEl);
446    test.addHTMLOutput(outputEl);
447  };
448
449  TestUtils.newInstantEvent = function(options) {
450    var title = options.title;
451    var start = options.start;
452    if ((title === undefined) ||
453        (title === '') ||
454        (start === undefined))
455      throw new Error('too little information');
456
457    var category = options.category || 'category';
458    var colorId = getColorId(options.colorId);
459    var args = options.args || {};
460    return new tr.model.InstantEvent(
461        category, title, colorId, start, args);
462  };
463
464  return {
465    TestUtils: TestUtils
466  };
467});
468</script>
469