Commit aba3d85b authored by Sigurd Schneider's avatar Sigurd Schneider Committed by Commit Bot

[devtools] Keep polling for coverage during performance tracing

This CL enables the coverage model to keep polling the back-end for
coverage data while performance tracing is enabled.

Bug: chromium:1004203
Change-Id: I187afa45dadae3aac0d5012fa8a293eb26d438fd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1814825Reviewed-by: default avatarYang Guo <yangguo@chromium.org>
Commit-Queue: Sigurd Schneider <sigurds@chromium.org>
Cr-Commit-Position: refs/heads/master@{#699718}
parent 2f563ea8
...@@ -49,6 +49,12 @@ Coverage.CoverageModel = class extends SDK.SDKModel { ...@@ -49,6 +49,12 @@ Coverage.CoverageModel = class extends SDK.SDKModel {
this._currentPollPromise = null; this._currentPollPromise = null;
/** @type {?boolean} */ /** @type {?boolean} */
this._shouldResumePollingOnResume = false; this._shouldResumePollingOnResume = false;
/** @type {!Array<!{rawCoverageData:!Array<!Protocol.Profiler.ScriptCoverage>,stamp:number}>} */
this._jsBacklog = [];
/** @type {!Array<!{rawCoverageData:!Array<!Protocol.CSS.RuleUsage>,stamp:number}>} */
this._cssBacklog = [];
/** @type {?boolean} */
this._performanceTraceRecording = false;
} }
/** /**
...@@ -103,12 +109,11 @@ Coverage.CoverageModel = class extends SDK.SDKModel { ...@@ -103,12 +109,11 @@ Coverage.CoverageModel = class extends SDK.SDKModel {
* @return {!Promise} * @return {!Promise}
*/ */
async _pollLoop() { async _pollLoop() {
const coveragePollingPeriodMs = 700;
this._clearTimer(); this._clearTimer();
this._currentPollPromise = this._pollAndCallback(); this._currentPollPromise = this._pollAndCallback();
await this._currentPollPromise; await this._currentPollPromise;
if (this._suspensionState === Coverage.SuspensionState.Active) if (this._suspensionState === Coverage.SuspensionState.Active || this._performanceTraceRecording)
this._pollTimer = setTimeout(() => this._pollLoop(), coveragePollingPeriodMs); this._pollTimer = setTimeout(() => this._pollLoop(), Coverage.CoverageModel._coveragePollingPeriodMs);
} }
async stopPolling() { async stopPolling() {
...@@ -123,13 +128,14 @@ Coverage.CoverageModel = class extends SDK.SDKModel { ...@@ -123,13 +128,14 @@ Coverage.CoverageModel = class extends SDK.SDKModel {
* @return {!Promise<undefined>} * @return {!Promise<undefined>}
*/ */
async _pollAndCallback() { async _pollAndCallback() {
if (this._suspensionState === Coverage.SuspensionState.Suspended) if (this._suspensionState === Coverage.SuspensionState.Suspended && !this._performanceTraceRecording)
return; return;
const updates = await this._takeAllCoverage(); const updates = await this._takeAllCoverage();
// This conditional should never trigger, as all intended ways to stop // This conditional should never trigger, as all intended ways to stop
// polling are awaiting the `_currentPollPromise` before suspending. // polling are awaiting the `_currentPollPromise` before suspending.
console.assert( console.assert(
this._suspensionState !== Coverage.SuspensionState.Suspended, 'CoverageModel was suspended while polling.'); this._suspensionState !== Coverage.SuspensionState.Suspended || this._performanceTraceRecording,
'CoverageModel was suspended while polling.');
if (updates.length) if (updates.length)
this.dispatchEventToListeners(Coverage.CoverageModel.Events.CoverageUpdated, updates); this.dispatchEventToListeners(Coverage.CoverageModel.Events.CoverageUpdated, updates);
} }
...@@ -145,12 +151,18 @@ Coverage.CoverageModel = class extends SDK.SDKModel { ...@@ -145,12 +151,18 @@ Coverage.CoverageModel = class extends SDK.SDKModel {
* Stops polling as preparation for suspension. This function is idempotent * Stops polling as preparation for suspension. This function is idempotent
* due because it changes the state to suspending. * due because it changes the state to suspending.
* @override * @override
* @param {string=} reason - optionally provide a reason, so the model can respond accordingly
* @return {!Promise<undefined>} * @return {!Promise<undefined>}
*/ */
async preSuspendModel() { async preSuspendModel(reason) {
if (this._suspensionState !== Coverage.SuspensionState.Active) if (this._suspensionState !== Coverage.SuspensionState.Active)
return; return;
this._suspensionState = Coverage.SuspensionState.Suspending; this._suspensionState = Coverage.SuspensionState.Suspending;
if (reason === 'performance-timeline') {
this._performanceTraceRecording = true;
// Keep polling to the backlog if a performance trace is recorded.
return;
}
if (this._currentPollPromise) { if (this._currentPollPromise) {
await this.stopPolling(); await this.stopPolling();
this._shouldResumePollingOnResume = true; this._shouldResumePollingOnResume = true;
...@@ -159,9 +171,10 @@ Coverage.CoverageModel = class extends SDK.SDKModel { ...@@ -159,9 +171,10 @@ Coverage.CoverageModel = class extends SDK.SDKModel {
/** /**
* @override * @override
* @param {string=} reason - optionally provide a reason, so the model can respond accordingly
* @return {!Promise<undefined>} * @return {!Promise<undefined>}
*/ */
async suspendModel() { async suspendModel(reason) {
this._suspensionState = Coverage.SuspensionState.Suspended; this._suspensionState = Coverage.SuspensionState.Suspended;
} }
...@@ -170,7 +183,6 @@ Coverage.CoverageModel = class extends SDK.SDKModel { ...@@ -170,7 +183,6 @@ Coverage.CoverageModel = class extends SDK.SDKModel {
* @return {!Promise<undefined>} * @return {!Promise<undefined>}
*/ */
async resumeModel() { async resumeModel() {
this._suspensionState = Coverage.SuspensionState.Active;
} }
/** /**
...@@ -180,9 +192,11 @@ Coverage.CoverageModel = class extends SDK.SDKModel { ...@@ -180,9 +192,11 @@ Coverage.CoverageModel = class extends SDK.SDKModel {
* @return {!Promise<undefined>} * @return {!Promise<undefined>}
*/ */
async postResumeModel() { async postResumeModel() {
this._suspensionState = Coverage.SuspensionState.Active;
this._performanceTraceRecording = false;
if (this._shouldResumePollingOnResume) { if (this._shouldResumePollingOnResume) {
await this.startPolling();
this._shouldResumePollingOnResume = false; this._shouldResumePollingOnResume = false;
await this.startPolling();
} }
} }
...@@ -235,25 +249,40 @@ Coverage.CoverageModel = class extends SDK.SDKModel { ...@@ -235,25 +249,40 @@ Coverage.CoverageModel = class extends SDK.SDKModel {
async _takeJSCoverage() { async _takeJSCoverage() {
if (!this._cpuProfilerModel) if (!this._cpuProfilerModel)
return []; return [];
let rawCoverageData = await this._cpuProfilerModel.takePreciseCoverage(); const now = Date.now();
let freshRawCoverageData = await this._cpuProfilerModel.takePreciseCoverage();
if (this._bestEffortCoveragePromise) { if (this._bestEffortCoveragePromise) {
const bestEffortCoverage = await this._bestEffortCoveragePromise; const bestEffortCoverage = await this._bestEffortCoveragePromise;
this._bestEffortCoveragePromise = null; this._bestEffortCoveragePromise = null;
rawCoverageData = bestEffortCoverage.concat(rawCoverageData); freshRawCoverageData = bestEffortCoverage.concat(freshRawCoverageData);
}
if (this._suspensionState !== Coverage.SuspensionState.Active) {
if (freshRawCoverageData.length > 0)
this._jsBacklog.push({rawCoverageData: freshRawCoverageData, stamp: now});
return [];
} }
return this._processJSCoverage(rawCoverageData); const results = [];
for (const {rawCoverageData, stamp} of this._jsBacklog)
results.push(this._processJSCoverage(rawCoverageData, stamp));
this._jsBacklog = [];
if (freshRawCoverageData.length > 0)
results.push(this._processJSCoverage(freshRawCoverageData, now));
return results.flat();
} }
/** /**
* @param {!Array<!Protocol.Profiler.ScriptCoverage>} scriptsCoverage * @param {!Array<!Protocol.Profiler.ScriptCoverage>} scriptsCoverage
* @return {!Array<!Coverage.CoverageInfo>} * @return {!Array<!Coverage.CoverageInfo>}
*/ */
_processJSCoverage(scriptsCoverage) { _processJSCoverage(scriptsCoverage, stamp) {
const updatedEntries = []; const updatedEntries = [];
for (const entry of scriptsCoverage) { for (const entry of scriptsCoverage) {
const script = this._debuggerModel.scriptForId(entry.scriptId); const script = this._debuggerModel.scriptForId(entry.scriptId);
if (!script) if (!script)
continue; continue;
const ranges = []; const ranges = [];
let type = Coverage.CoverageType.JavaScript; let type = Coverage.CoverageType.JavaScript;
for (const func of entry.functions) { for (const func of entry.functions) {
...@@ -268,7 +297,7 @@ Coverage.CoverageModel = class extends SDK.SDKModel { ...@@ -268,7 +297,7 @@ Coverage.CoverageModel = class extends SDK.SDKModel {
} }
const subentry = this._addCoverage( const subentry = this._addCoverage(
script, script.contentLength, script.lineOffset, script.columnOffset, ranges, script, script.contentLength, script.lineOffset, script.columnOffset, ranges,
/** @type {!Coverage.CoverageType} */ (type)); /** @type {!Coverage.CoverageType} */ (type), stamp);
if (subentry) if (subentry)
updatedEntries.push(subentry); updatedEntries.push(subentry);
} }
...@@ -281,15 +310,29 @@ Coverage.CoverageModel = class extends SDK.SDKModel { ...@@ -281,15 +310,29 @@ Coverage.CoverageModel = class extends SDK.SDKModel {
async _takeCSSCoverage() { async _takeCSSCoverage() {
if (!this._cssModel) if (!this._cssModel)
return []; return [];
const rawCoverageData = await this._cssModel.takeCoverageDelta(); const now = Date.now();
return this._processCSSCoverage(rawCoverageData); const freshRawCoverageData = await this._cssModel.takeCoverageDelta();
if (this._suspensionState !== Coverage.SuspensionState.Active) {
if (freshRawCoverageData.length > 0)
this._cssBacklog.push({rawCoverageData: freshRawCoverageData, stamp: now});
return [];
}
const results = [];
for (const {rawCoverageData, stamp} of this._cssBacklog)
results.push(this._processCSSCoverage(rawCoverageData, stamp));
this._cssBacklog = [];
if (freshRawCoverageData.length > 0)
results.push(this._processCSSCoverage(freshRawCoverageData, now));
return results.flat();
} }
/** /**
* @param {!Array<!Protocol.CSS.RuleUsage>} ruleUsageList * @param {!Array<!Protocol.CSS.RuleUsage>} ruleUsageList
* @return {!Array<!Coverage.CoverageInfo>} * @return {!Array<!Coverage.CoverageInfo>}
*/ */
_processCSSCoverage(ruleUsageList) { _processCSSCoverage(ruleUsageList, stamp) {
const updatedEntries = []; const updatedEntries = [];
/** @type {!Map<!SDK.CSSStyleSheetHeader, !Array<!Coverage.RangeUseCount>>} */ /** @type {!Map<!SDK.CSSStyleSheetHeader, !Array<!Coverage.RangeUseCount>>} */
const rulesByStyleSheet = new Map(); const rulesByStyleSheet = new Map();
...@@ -309,7 +352,7 @@ Coverage.CoverageModel = class extends SDK.SDKModel { ...@@ -309,7 +352,7 @@ Coverage.CoverageModel = class extends SDK.SDKModel {
const ranges = /** @type {!Array<!Coverage.RangeUseCount>} */ (entry[1]); const ranges = /** @type {!Array<!Coverage.RangeUseCount>} */ (entry[1]);
const subentry = this._addCoverage( const subentry = this._addCoverage(
styleSheetHeader, styleSheetHeader.contentLength, styleSheetHeader.startLine, styleSheetHeader.startColumn, styleSheetHeader, styleSheetHeader.contentLength, styleSheetHeader.startLine, styleSheetHeader.startColumn,
ranges, Coverage.CoverageType.CSS); ranges, Coverage.CoverageType.CSS, stamp);
if (subentry) if (subentry)
updatedEntries.push(subentry); updatedEntries.push(subentry);
} }
...@@ -320,7 +363,7 @@ Coverage.CoverageModel = class extends SDK.SDKModel { ...@@ -320,7 +363,7 @@ Coverage.CoverageModel = class extends SDK.SDKModel {
* @param {!Array<!Coverage.RangeUseCount>} ranges * @param {!Array<!Coverage.RangeUseCount>} ranges
* @return {!Array<!Coverage.CoverageSegment>} * @return {!Array<!Coverage.CoverageSegment>}
*/ */
static _convertToDisjointSegments(ranges) { static _convertToDisjointSegments(ranges, stamp) {
ranges.sort((a, b) => a.startOffset - b.startOffset); ranges.sort((a, b) => a.startOffset - b.startOffset);
const result = []; const result = [];
...@@ -355,7 +398,7 @@ Coverage.CoverageModel = class extends SDK.SDKModel { ...@@ -355,7 +398,7 @@ Coverage.CoverageModel = class extends SDK.SDKModel {
return; return;
} }
} }
result.push({end: end, count: count}); result.push({end: end, count: count, stamp: stamp});
} }
return result; return result;
...@@ -370,7 +413,7 @@ Coverage.CoverageModel = class extends SDK.SDKModel { ...@@ -370,7 +413,7 @@ Coverage.CoverageModel = class extends SDK.SDKModel {
* @param {!Coverage.CoverageType} type * @param {!Coverage.CoverageType} type
* @return {?Coverage.CoverageInfo} * @return {?Coverage.CoverageInfo}
*/ */
_addCoverage(contentProvider, contentLength, startLine, startColumn, ranges, type) { _addCoverage(contentProvider, contentLength, startLine, startColumn, ranges, type, stamp) {
const url = contentProvider.contentURL(); const url = contentProvider.contentURL();
if (!url) if (!url)
return null; return null;
...@@ -382,9 +425,9 @@ Coverage.CoverageModel = class extends SDK.SDKModel { ...@@ -382,9 +425,9 @@ Coverage.CoverageModel = class extends SDK.SDKModel {
const coverageInfo = urlCoverage._ensureEntry(contentProvider, contentLength, startLine, startColumn, type); const coverageInfo = urlCoverage._ensureEntry(contentProvider, contentLength, startLine, startColumn, type);
this._coverageByContentProvider.set(contentProvider, coverageInfo); this._coverageByContentProvider.set(contentProvider, coverageInfo);
const segments = Coverage.CoverageModel._convertToDisjointSegments(ranges); const segments = Coverage.CoverageModel._convertToDisjointSegments(ranges, stamp);
if (segments.length && segments.peekLast().end < contentLength) if (segments.length && segments.peekLast().end < contentLength)
segments.push({end: contentLength}); segments.push({end: contentLength, stamp: stamp});
const oldUsedSize = coverageInfo._usedSize; const oldUsedSize = coverageInfo._usedSize;
coverageInfo.mergeCoverage(segments); coverageInfo.mergeCoverage(segments);
if (coverageInfo._usedSize === oldUsedSize) if (coverageInfo._usedSize === oldUsedSize)
...@@ -464,13 +507,16 @@ Coverage.CoverageModel = class extends SDK.SDKModel { ...@@ -464,13 +507,16 @@ Coverage.CoverageModel = class extends SDK.SDKModel {
} }
}; };
SDK.SDKModel.register(Coverage.CoverageModel, SDK.Target.Capability.None, false);
/** @enum {symbol} */ /** @enum {symbol} */
Coverage.CoverageModel.Events = { Coverage.CoverageModel.Events = {
CoverageUpdated: Symbol('CoverageUpdated') CoverageUpdated: Symbol('CoverageUpdated')
}; };
/** @type {number} */
Coverage.CoverageModel._coveragePollingPeriodMs = 200;
SDK.SDKModel.register(Coverage.CoverageModel, SDK.Target.Capability.None, false);
Coverage.URLCoverageInfo = class { Coverage.URLCoverageInfo = class {
/** /**
* @param {string} url * @param {string} url
...@@ -528,6 +574,10 @@ Coverage.URLCoverageInfo = class { ...@@ -528,6 +574,10 @@ Coverage.URLCoverageInfo = class {
return this._isContentScript; return this._isContentScript;
} }
entries() {
return this._coverageInfoByLocation.values();
}
/** /**
* @param {!Common.ContentProvider} contentProvider * @param {!Common.ContentProvider} contentProvider
* @param {number} contentLength * @param {number} contentLength
...@@ -572,6 +622,7 @@ Coverage.CoverageInfo = class { ...@@ -572,6 +622,7 @@ Coverage.CoverageInfo = class {
this._contentProvider = contentProvider; this._contentProvider = contentProvider;
this._size = size; this._size = size;
this._usedSize = 0; this._usedSize = 0;
this._statsByTimestamp = new Map();
this._lineOffset = lineOffset; this._lineOffset = lineOffset;
this._columnOffset = columnOffset; this._columnOffset = columnOffset;
this._coverageType = type; this._coverageType = type;
...@@ -609,6 +660,14 @@ Coverage.CoverageInfo = class { ...@@ -609,6 +660,14 @@ Coverage.CoverageInfo = class {
this._updateStats(); this._updateStats();
} }
usedByTimestamp() {
return this._statsByTimestamp;
}
size() {
return this._size;
}
/** /**
* @param {number} start * @param {number} start
* @param {number} end * @param {number} end
...@@ -639,8 +698,9 @@ Coverage.CoverageInfo = class { ...@@ -639,8 +698,9 @@ Coverage.CoverageInfo = class {
typeof a.count === 'number' || typeof b.count === 'number' ? (a.count || 0) + (b.count || 0) : undefined; typeof a.count === 'number' || typeof b.count === 'number' ? (a.count || 0) + (b.count || 0) : undefined;
const end = Math.min(a.end, b.end); const end = Math.min(a.end, b.end);
const last = result.peekLast(); const last = result.peekLast();
if (!last || last.count !== count) const stamp = Math.min(a.stamp, b.stamp);
result.push({end: end, count: count}); if (!last || last.count !== count || last.stamp !== stamp)
result.push({end: end, count: count, stamp: stamp});
else else
last.end = end; last.end = end;
if (a.end <= b.end) if (a.end <= b.end)
...@@ -657,12 +717,19 @@ Coverage.CoverageInfo = class { ...@@ -657,12 +717,19 @@ Coverage.CoverageInfo = class {
} }
_updateStats() { _updateStats() {
this._statsByTimestamp = new Map();
this._usedSize = 0; this._usedSize = 0;
let last = 0; let last = 0;
for (const segment of this._segments) { for (const segment of this._segments) {
if (segment.count) if (!this._statsByTimestamp.has(segment.stamp))
this._usedSize += segment.end - last; this._statsByTimestamp.set(segment.stamp, 0);
if (segment.count) {
const used = segment.end - last;
this._usedSize += used;
this._statsByTimestamp.set(segment.stamp, this._statsByTimestamp.get(segment.stamp) + used);
}
last = segment.end; last = segment.end;
} }
} }
......
...@@ -198,15 +198,16 @@ SDK.Target = class extends Protocol.TargetBase { ...@@ -198,15 +198,16 @@ SDK.Target = class extends Protocol.TargetBase {
} }
/** /**
* @param {string=} reason - optionally provide a reason, so models can respond accordingly
* @return {!Promise} * @return {!Promise}
*/ */
async suspend() { async suspend(reason) {
if (this._isSuspended) if (this._isSuspended)
return Promise.resolve(); return Promise.resolve();
this._isSuspended = true; this._isSuspended = true;
await Promise.all(Array.from(this.models().values(), m => m.preSuspendModel())); await Promise.all(Array.from(this.models().values(), m => m.preSuspendModel(reason)));
await Promise.all(Array.from(this.models().values(), m => m.suspendModel())); await Promise.all(Array.from(this.models().values(), m => m.suspendModel(reason)));
} }
/** /**
...@@ -285,16 +286,18 @@ SDK.SDKModel = class extends Common.Object { ...@@ -285,16 +286,18 @@ SDK.SDKModel = class extends Common.Object {
/** /**
* Override this method to perform tasks that are required to suspend the * Override this method to perform tasks that are required to suspend the
* model and that still need other models in an unsuspended state. * model and that still need other models in an unsuspended state.
* @param {string=} reason - optionally provide a reason, the model can respond accordingly
* @return {!Promise} * @return {!Promise}
*/ */
preSuspendModel() { preSuspendModel(reason) {
return Promise.resolve(); return Promise.resolve();
} }
/** /**
* @param {string=} reason - optionally provide a reason, the model can respond accordingly
* @return {!Promise} * @return {!Promise}
*/ */
suspendModel() { suspendModel(reason) {
return Promise.resolve(); return Promise.resolve();
} }
......
...@@ -19,14 +19,15 @@ SDK.TargetManager = class extends Common.Object { ...@@ -19,14 +19,15 @@ SDK.TargetManager = class extends Common.Object {
} }
/** /**
* @param {string=} reason - optionally provide a reason, so targets can respond accordingly
* @return {!Promise} * @return {!Promise}
*/ */
suspendAllTargets() { suspendAllTargets(reason) {
if (this._isSuspended) if (this._isSuspended)
return Promise.resolve(); return Promise.resolve();
this._isSuspended = true; this._isSuspended = true;
this.dispatchEventToListeners(SDK.TargetManager.Events.SuspendStateChanged); this.dispatchEventToListeners(SDK.TargetManager.Events.SuspendStateChanged);
return Promise.all(this._targets.map(target => target.suspend())); return Promise.all(this._targets.map(target => target.suspend(reason)));
} }
/** /**
......
...@@ -187,7 +187,7 @@ Timeline.TimelineController = class { ...@@ -187,7 +187,7 @@ Timeline.TimelineController = class {
// There might be a significant delay in the beginning of timeline recording // There might be a significant delay in the beginning of timeline recording
// caused by starting CPU profiler, that needs to traverse JS heap to collect // caused by starting CPU profiler, that needs to traverse JS heap to collect
// all the functions data. // all the functions data.
await SDK.targetManager.suspendAllTargets(); await SDK.targetManager.suspendAllTargets('performance-timeline');
if (enableJSSampling && Runtime.queryParam('timelineTracingJSProfileDisabled')) if (enableJSSampling && Runtime.queryParam('timelineTracingJSProfileDisabled'))
await this._startProfilingOnAllModels(); await this._startProfilingOnAllModels();
if (!this._tracingManager) if (!this._tracingManager)
......
...@@ -3,22 +3,35 @@ Tests the merge of disjoint segment lists in CoverageModel. ...@@ -3,22 +3,35 @@ Tests the merge of disjoint segment lists in CoverageModel.
A: [] A: []
B: [] B: []
merged: [] merged: []
A: [{"end":10,"count":1}] A: [{"end":10,"count":1,"stamp":100}]
B: [] B: []
merged: [{"end":10,"count":1}] merged: [{"end":10,"count":1,"stamp":100}]
A: [{"end":10,"count":1}] A: [{"end":10,"count":1,"stamp":100}]
B: [{"end":10,"count":1}] B: [{"end":10,"count":1,"stamp":100}]
merged: [{"end":10,"count":2}] merged: [{"end":10,"count":2,"stamp":100}]
A: [{"end":10,"count":1}] A: [{"end":10,"count":1,"stamp":100}]
B: [{"end":20,"count":1}] B: [{"end":20,"count":1,"stamp":100}]
merged: [{"end":10,"count":2},{"end":20,"count":1}] merged: [{"end":10,"count":2,"stamp":100},{"end":20,"count":1,"stamp":100}]
A: [{"end":10,"count":1},{"end":20,"count":1}] A: [{"end":10,"count":1,"stamp":100},{"end":20,"count":1,"stamp":100}]
B: [] B: []
merged: [{"end":10,"count":1},{"end":20,"count":1}] merged: [{"end":10,"count":1,"stamp":100},{"end":20,"count":1,"stamp":100}]
A: [{"end":30,"count":1}] A: [{"end":30,"count":1,"stamp":100}]
B: [{"end":10},{"end":20,"count":2}] B: [{"end":10,"stamp":100},{"end":20,"count":2,"stamp":100}]
merged: [{"end":10,"count":1},{"end":20,"count":3},{"end":30,"count":1}] merged: [{"end":10,"count":1,"stamp":100},{"end":20,"count":3,"stamp":100},{"end":30,"count":1,"stamp":100}]
A: [{"end":30}] A: [{"end":30,"stamp":100}]
B: [{"end":10},{"end":20,"count":2}] B: [{"end":10,"stamp":100},{"end":20,"count":2,"stamp":100}]
merged: [{"end":10},{"end":20,"count":2},{"end":30}] merged: [{"end":10,"stamp":100},{"end":20,"count":2,"stamp":100},{"end":30,"stamp":100}]
Merging different stamps should result in the minimum timestamp
A: [{"end":10,"count":1,"stamp":100}]
B: [{"end":10,"count":1,"stamp":200}]
merged: [{"end":10,"count":2,"stamp":100}]
A: [{"end":10,"count":1,"stamp":100}]
B: [{"end":20,"count":1,"stamp":200}]
merged: [{"end":10,"count":2,"stamp":100},{"end":20,"count":1,"stamp":200}]
A: [{"end":10,"count":1,"stamp":100},{"end":20,"count":1,"stamp":200}]
B: []
merged: [{"end":10,"count":1,"stamp":100},{"end":20,"count":1,"stamp":200}]
A: [{"end":30,"count":1,"stamp":100}]
B: [{"end":10,"stamp":100},{"end":20,"count":2,"stamp":200}]
merged: [{"end":10,"count":1,"stamp":100},{"end":20,"count":3,"stamp":100},{"end":30,"count":1,"stamp":100}]
...@@ -7,12 +7,18 @@ ...@@ -7,12 +7,18 @@
await TestRunner.loadModule('coverage'); await TestRunner.loadModule('coverage');
testAndDump([], []); testAndDump([], []);
testAndDump([{end: 10, count: 1}], []); testAndDump([{end: 10, count: 1, stamp: 100}], []);
testAndDump([{end: 10, count: 1}], [{end: 10, count: 1}]); testAndDump([{end: 10, count: 1, stamp: 100}], [{end: 10, count: 1, stamp: 100}]);
testAndDump([{end: 10, count: 1}], [{end: 20, count: 1}]); testAndDump([{end: 10, count: 1, stamp: 100}], [{end: 20, count: 1, stamp: 100}]);
testAndDump([{end: 10, count: 1}, {end: 20, count: 1}], []); testAndDump([{end: 10, count: 1, stamp: 100}, {end: 20, count: 1, stamp: 100}], []);
testAndDump([{end: 30, count: 1}], [{end: 10, count: undefined}, {end: 20, count: 2}]); testAndDump([{end: 30, count: 1, stamp: 100}], [{end: 10, count: undefined, stamp: 100}, {end: 20, count: 2, stamp: 100}]);
testAndDump([{end: 30, count: undefined}], [{end: 10, count: undefined}, {end: 20, count: 2}]); testAndDump([{end: 30, count: undefined, stamp: 100}], [{end: 10, count: undefined, stamp: 100}, {end: 20, count: 2, stamp: 100}]);
TestRunner.addResult(`Merging different stamps should result in the minimum timestamp`);
testAndDump([{end: 10, count: 1, stamp: 100}], [{end: 10, count: 1, stamp: 200}]);
testAndDump([{end: 10, count: 1, stamp: 100}], [{end: 20, count: 1, stamp: 200}]);
testAndDump([{end: 10, count: 1, stamp: 100}, {end: 20, count: 1, stamp: 200}], []);
testAndDump([{end: 30, count: 1, stamp: 100}], [{end: 10, count: undefined, stamp: 100}, {end: 20, count: 2, stamp: 200}]);
TestRunner.completeTest(); TestRunner.completeTest();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment