/**
* @license
* Copyright The Closure Library Authors.
* SPDX-License-Identifier: Apache-2.0
*/
goog.module('goog.debug.TraceTest');
goog.setTestOnly();
const StopTraceDetail = goog.requireType('goog.debug.StopTraceDetail');
const Trace = goog.require('goog.debug.Trace');
const googArray = goog.require('goog.array');
const recordFunction = goog.require('goog.testing.recordFunction');
const testSuite = goog.require('goog.testing.testSuite');
/** @type {!Function} */
const recorder = recordFunction();
/** @const {!StopTraceDetail} */
const TRACE_CANCELLED = {
wasCancelled: true,
};
/** @const {!StopTraceDetail} */
const NORMAL_STOP = {};
/**
* Checks if the actual log of a fake listener matches the expectations.
* @param {!Array<!Array<*>>} expected The expected log from the fake listener.
* @param {!Function} recorder The output of `recordFunction` for logging all
* calls to the fake listener.
* @return {boolean} True if the recorder's log is expected.
* @suppress {strictMissingProperties,missingReturn} suppression added to enable
* type checking
*/
function validateRecordedListener(expected, recorder) {
assertObjectEquals(
expected,
googArray.map(recorder.getCalls(), (call) => call.getArguments()));
}
testSuite({
/**
@suppress {strictMissingProperties,checkTypes} suppression added to enable
type checking
*/
setUp() {
Trace.initCurrentTrace();
Trace.removeAllListeners();
recorder.reset();
},
testProperEventReleaseViaResetForComment() {
Trace.startTracer('foo');
// The Start event and its id are released due to calling the reset method.
Trace.clearCurrentTrace();
// Recycling the last Start event.
Trace.addComment('abc');
Trace.startTracer('foo');
Trace.clearCurrentTrace();
const t1 = Trace.startTracer('f1');
const t2 = Trace.startTracer('f2');
assertNotEquals('The trace ids cannot repeat.', t1, t2);
},
testProperEventReleaseViaThresholdForComment() {
const t3 = Trace.startTracer('foo');
// The Start event and its id are released due to 1000ms threshold.
Trace.stopTracer(t3, 1000);
// Recycling the last Start event.
Trace.addComment('abc');
Trace.startTracer('foo');
Trace.clearCurrentTrace();
const t1 = Trace.startTracer('f1');
const t2 = Trace.startTracer('f2');
assertNotEquals('The trace ids cannot repeat.', t1, t2);
},
testProperEventReleaseViaResetForStop() {
Trace.startTracer('foo');
Trace.startTracer('foo');
// The Start events and their ids are released because of reseting.
Trace.clearCurrentTrace();
// Recycling the last two Start events.
const t0 = Trace.startTracer('foo');
Trace.stopTracer(t0);
Trace.clearCurrentTrace();
const t1 = Trace.startTracer('fa');
Trace.startTracer('fb');
const t2 = Trace.startTracer('fc');
// No id is repeated.
assertNotEquals('The trace ids cannot repeat.', t1, t2);
},
testProperEventReleaseViaThresholdForStop() {
let t1 = Trace.startTracer('f1');
let t2 = Trace.startTracer('f2');
// The Start events and their ids are released due to 1000ms threshold.
Trace.stopTracer(t2, 1000);
Trace.stopTracer(t1, 1000);
// Recycling the last two Start events.
const t0 = Trace.startTracer('foo');
Trace.stopTracer(t0);
Trace.clearCurrentTrace();
t1 = Trace.startTracer('fa');
Trace.startTracer('fb');
t2 = Trace.startTracer('fc');
// No id is repeated.
assertNotEquals('The trace ids cannot repeat.', t1, t2);
},
testTracer() {
const t = Trace.startTracer('foo');
let sum = 0;
for (let i = 0; i < 100000; i++) {
sum += i;
}
Trace.stopTracer(t);
const trace = Trace.getFormattedTrace();
const lines = trace.split('\n');
assertEquals(8, lines.length);
assertNotNull(lines[0].match(/^\s*\d+\.\d+\s+Start\s+foo$/));
assertNotNull(lines[1].match(/^\s*\d+\s+\d+\.\d+\s+Done\s+\d+ ms\s+foo$/));
},
testListenerTooManyOpenTraces() {
Trace.addTraceCallbacks({
start: goog.partial(recorder, 'start'),
stop: goog.partial(recorder, 'stop'),
});
const expected = [];
const openTraces = [];
for (let i = 0; 2 * i <= Trace.MAX_TRACE_SIZE; i++) {
const t = Trace.startTracer('trace');
expected.push(['start', t, 'trace']);
openTraces.push(t);
}
// Triggering the giant thread warning to clear open traces.
const t = Trace.startTracer('last');
for (let j = 0; 2 * j <= Trace.MAX_TRACE_SIZE; j++) {
expected.push(['stop', openTraces[j], TRACE_CANCELLED]);
}
expected.push(['start', t, 'last']);
validateRecordedListener(expected, recorder);
},
testListenerGiantThread() {
Trace.addTraceCallbacks({
start: goog.partial(recorder, 'start'),
stop: goog.partial(recorder, 'stop'),
});
const t1 = Trace.startTracer('first');
const expected = [['start', t1, 'first']];
let t;
for (let i = 0; 2 * i < Trace.MAX_TRACE_SIZE; i++) {
t = Trace.startTracer('trace');
Trace.stopTracer(t);
expected.push(['start', t, 'trace'], ['stop', t, NORMAL_STOP]);
}
// Triggering the giant thread warning.
const t2 = Trace.startTracer('last');
expected.push(['start', t2, 'last']);
// Make sure that the last id of the giant thread is released after
// clearing.
assertEquals('The last id is not recycled!', t, t2);
// Make sure that t1 and t2 are not stopped/cancelled.
validateRecordedListener(expected, recorder);
},
testListenerReset() {
Trace.addTraceCallbacks({
start: goog.partial(recorder, 'start'),
stop: goog.partial(recorder, 'stop'),
});
const t1 = Trace.startTracer('1st');
const t2 = Trace.startTracer('2nd');
Trace.stopTracer(t2);
const t3 = Trace.startTracer('3rd');
// Forcing t1 and t3 to be cancelled.
Trace.clearCurrentTrace();
const expected = [
['start', t1, '1st'],
['start', t2, '2nd'],
['stop', t2, NORMAL_STOP],
['start', t3, '3rd'],
['stop', t1, TRACE_CANCELLED],
['stop', t3, TRACE_CANCELLED],
];
validateRecordedListener(expected, recorder);
},
/** @suppress {checkTypes} suppression added to enable type checking */
testRecord() {
/** @type {number} */
const a = 10;
Trace.addTraceCallbacks(a);
},
testListenerStopTracerSilence() {
Trace.addTraceCallbacks({
start: goog.partial(recorder, 'start'),
stop: goog.partial(recorder, 'stop'),
comment: goog.partial(recorder, 'comment'),
});
const t = Trace.startTracer('first');
// 1000ms should be enough for silencing the tracer.
Trace.stopTracer(t, 1000);
validateRecordedListener(
[['start', t, 'first'], ['stop', t, NORMAL_STOP]], recorder);
},
testListenerStartTracerType() {
Trace.addTraceCallbacks({
start: goog.partial(recorder, 'start'),
stop: goog.partial(recorder, 'stop'),
comment: goog.partial(recorder, 'comment'),
});
const t = Trace.startTracer('first', 'New Type');
Trace.stopTracer(t);
validateRecordedListener(
[['start', t, '[New Type] first'], ['stop', t, NORMAL_STOP]], recorder);
},
testListenerCommentTracerType() {
Trace.addTraceCallbacks({
start: goog.partial(recorder, 'start'),
stop: goog.partial(recorder, 'stop'),
comment: goog.partial(recorder, 'comment'),
});
Trace.addComment('foo', 'bar');
validateRecordedListener([['comment', '[bar] foo']], recorder);
},
testListenerCommentTracerTime() {
Trace.addTraceCallbacks({
start: goog.partial(recorder, 'start'),
stop: goog.partial(recorder, 'stop'),
comment: goog.partial(recorder, 'comment'),
});
const timestamp = goog.now() - 10;
Trace.addComment('first', null, timestamp);
validateRecordedListener([['comment', 'first', timestamp]], recorder);
},
testListenerCommentTracerAlone() {
Trace.addTraceCallbacks({
comment: goog.partial(recorder, 'comment'),
});
Trace.addComment('foo');
validateRecordedListener([['comment', 'foo']], recorder);
},
testListenerCommentTracerNoLog() {
Trace.addTraceCallbacks({
start: goog.partial(recorder, 'start'),
stop: goog.partial(recorder, 'stop'),
});
Trace.addComment('foo');
validateRecordedListener([], recorder);
},
testListenerStartStopTracerOnly() {
Trace.addTraceCallbacks({
start: goog.partial(recorder, 'start'),
stop: goog.partial(recorder, 'stop'),
});
const t = Trace.startTracer('bar');
Trace.stopTracer(t);
validateRecordedListener(
[['start', t, 'bar'], ['stop', t, NORMAL_STOP]], recorder);
},
testTwoListeners() {
const r1 = recordFunction();
Trace.addTraceCallbacks({
start: goog.partial(r1, 'start'),
stop: goog.partial(r1, 'stop'),
});
const t0 = Trace.startTracer('first');
Trace.stopTracer(t0);
const expected1 = [['start', t0, 'first'], ['stop', t0, NORMAL_STOP]];
validateRecordedListener(expected1, r1);
const r2 = recordFunction();
Trace.addTraceCallbacks({
start: goog.partial(r2, 'start'),
stop: goog.partial(r2, 'stop'),
comment: goog.partial(r2, 'comment'),
});
const t1 = Trace.startTracer('second', 'XType');
const t2 = Trace.startTracer('third');
Trace.addComment('NoTime');
const currentTime = goog.now();
Trace.addComment('WithTime', null, currentTime);
Trace.addComment('NoTime', 'YType');
Trace.stopTracer(t2);
Trace.stopTracer(t1);
const expected2 = [
['start', t1, '[XType] second'],
['start', t2, 'third'],
['comment', 'NoTime'],
['comment', 'WithTime', currentTime],
['comment', '[YType] NoTime'],
['stop', t2, NORMAL_STOP],
['stop', t1, NORMAL_STOP],
];
validateRecordedListener(expected2, r2);
const expectedNoComment = [
['start', t0, 'first'],
['stop', t0, NORMAL_STOP],
['start', t1, '[XType] second'],
['start', t2, 'third'],
['stop', t2, NORMAL_STOP],
['stop', t1, NORMAL_STOP],
];
validateRecordedListener(expectedNoComment, r1);
},
});