Skip to content

Commit efce5da

Browse files
jackfranklinDevtools-frontend LUCI CQ
authored andcommitted
RPP: cache SyntheticEventsManager on rawEvents array to fix tests
We have had some tests on CI that fail when run as part of the overall DevTools suite, but pass in isolation. One of the reasons this happens is because of how the SyntheticEventsManager is initialised. In tests, because we cache and don't re-run model.parse() on the same trace, we end up not having the right active SyntheticEventsManager for a given trace. The test will then pass if run in isolation because when it is the only test running it has the correct data setup. To fix this I modified the SyntheticEventsManager; now it caches instances based on the rawEvents() array, and tracks the active one. We did support fetching by an index, but the API didn't actually ever return the user what the index for a given trace was, so this was useless, and we only ever care about the active one anyway. Bug: none Change-Id: Ie709b8bff370c395e3eeed2029fddd5086a3b2b4 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/5687953 Reviewed-by: Alina Varkki <alinavarkki@chromium.org> Commit-Queue: Jack Franklin <jacktfranklin@chromium.org> Auto-Submit: Jack Franklin <jacktfranklin@chromium.org>
1 parent a25e31f commit efce5da

10 files changed

Lines changed: 63 additions & 59 deletions

File tree

‎front_end/models/trace/ModelImpl.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,7 @@ export class Model<EnabledModelHandlers extends {[key: string]: Handlers.Types.T
106106
try {
107107
// Wait for all outstanding promises before finishing the async execution,
108108
// but perform all tasks in parallel.
109-
const syntheticEventsManager =
110-
Helpers.SyntheticEvents.SyntheticEventsManager.initSyntheticEventsManagerForTrace(traceEvents);
109+
const syntheticEventsManager = Helpers.SyntheticEvents.SyntheticEventsManager.initAndActivate(traceEvents);
111110
await this.#processor.parse(traceEvents, isFreshRecording);
112111
this.#storeParsedFileData(file, this.#processor.traceParsedData, this.#processor.insights);
113112
// We only push the file onto this.#traces here once we know it's valid

‎front_end/models/trace/handlers/ScreenshotsHandler.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ describe('ScreenshotsHandler', function() {
4242
{...baseEvent, ts: TraceModel.Types.Timing.MicroSeconds(100)},
4343
{...baseEvent, ts: TraceModel.Types.Timing.MicroSeconds(200)},
4444
];
45-
TraceModel.Helpers.SyntheticEvents.SyntheticEventsManager.initSyntheticEventsManagerForTrace(baseEvents);
45+
TraceModel.Helpers.SyntheticEvents.SyntheticEventsManager.initAndActivate(baseEvents);
4646

4747
for (const event of baseEvents) {
4848
TraceModel.Handlers.ModelHandlers.Meta.handleEvent(event);

‎front_end/models/trace/helpers/SyntheticEvents.test.ts‎

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,32 +9,37 @@ describe('SyntheticEvents', function() {
99
beforeEach(() => {
1010
TraceModel.Helpers.SyntheticEvents.SyntheticEventsManager.reset();
1111
});
12+
1213
afterEach(() => {
1314
TraceModel.Helpers.SyntheticEvents.SyntheticEventsManager.reset();
1415
});
16+
1517
describe('Initialization', function() {
1618
it('does not throw when invoking getActiveManager after executing the trace engine', async function() {
1719
const events = await TraceLoader.fixtureContents(this, 'basic.json.gz');
1820
await TraceLoader.executeTraceEngineOnFileContents(events);
1921
assert.doesNotThrow(TraceModel.Helpers.SyntheticEvents.SyntheticEventsManager.getActiveManager);
2022
});
2123

22-
it('returns the last active SyntheticEventsManager with getActiveManager', async function() {
23-
const events = await TraceLoader.fixtureContents(this, 'basic.json.gz');
24-
await TraceLoader.executeTraceEngineOnFileContents(events);
25-
const firstActiveManager = TraceModel.Helpers.SyntheticEvents.SyntheticEventsManager.getActiveManager();
26-
await TraceLoader.executeTraceEngineOnFileContents(events);
27-
const secondActiveManager = TraceModel.Helpers.SyntheticEvents.SyntheticEventsManager.getActiveManager();
28-
assert.notEqual(firstActiveManager, secondActiveManager);
29-
});
30-
it('returns the SyntheticEventsManager for a given trace index with getManagerForTrace', async function() {
31-
const events = await TraceLoader.fixtureContents(this, 'basic.json.gz');
32-
await TraceLoader.executeTraceEngineOnFileContents(events);
33-
const firstActiveManager = TraceModel.Helpers.SyntheticEvents.SyntheticEventsManager.getActiveManager();
34-
await TraceLoader.executeTraceEngineOnFileContents(events);
35-
const testActiveManager = TraceModel.Helpers.SyntheticEvents.SyntheticEventsManager.getManagerForTrace(0);
36-
assert.strictEqual(testActiveManager, firstActiveManager);
37-
});
24+
it('always returns the manager for the most recent trace and re-uses managers for the same set of input events',
25+
async function() {
26+
// Exact traces do not matter, as long as they are different
27+
const trace1 = await TraceLoader.fixtureContents(this, 'basic.json.gz');
28+
const trace2 = await TraceLoader.fixtureContents(this, 'basic-stack.json.gz');
29+
30+
const events1 = Array.isArray(trace1) ? trace1 : trace1.traceEvents;
31+
const events2 = Array.isArray(trace2) ? trace2 : trace2.traceEvents;
32+
33+
const manager1 = TraceModel.Helpers.SyntheticEvents.SyntheticEventsManager.initAndActivate(events1);
34+
const manager2 = TraceModel.Helpers.SyntheticEvents.SyntheticEventsManager.initAndActivate(events2);
35+
36+
// Manager2 is active as it was the last one to be initialized
37+
assert.strictEqual(TraceModel.Helpers.SyntheticEvents.SyntheticEventsManager.getActiveManager(), manager2);
38+
// Now re-init with events1 (should use the existing manager from the cache)
39+
TraceModel.Helpers.SyntheticEvents.SyntheticEventsManager.initAndActivate(events1);
40+
// Now manager1 is active
41+
assert.strictEqual(TraceModel.Helpers.SyntheticEvents.SyntheticEventsManager.getActiveManager(), manager1);
42+
});
3843
});
3944

4045
describe('SyntheticBasedEvent registration', () => {

‎front_end/models/trace/helpers/SyntheticEvents.ts‎

Lines changed: 18 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import type * as Types from '../types/types.js';
66

77
const syntheticEventsManagerByTraceIndex: SyntheticEventsManager[] = [];
88

9+
const managerByRawEvents = new Map<readonly Types.TraceEvents.TraceEventData[], SyntheticEventsManager>();
10+
11+
let activeManager: SyntheticEventsManager|null = null;
12+
913
export class SyntheticEventsManager {
1014
/**
1115
* All synthetic entries created in a trace from a corresponding trace events.
@@ -22,45 +26,31 @@ export class SyntheticEventsManager {
2226
* called before running the trace engine handlers, since the instance
2327
* created here will be used by the handlers to register their
2428
* synthetic trace events.
29+
*
30+
* Can be called multiple times for the same set of raw events, in which case it will re-use the existing manager rather than recreate it again.
2531
*/
26-
static initSyntheticEventsManagerForTrace(rawEvents: readonly Types.TraceEvents.TraceEventData[]):
27-
SyntheticEventsManager {
28-
const manager = new SyntheticEventsManager(rawEvents);
29-
syntheticEventsManagerByTraceIndex.push(manager);
30-
return manager;
31-
}
32-
33-
/**
34-
* Gets the SyntheticEventsManager instance for a trace given the index
35-
* of the trace given its index used in Model#traces. If no index is
36-
* passed, defaults to the last created instance.
37-
* If no instance is found throws error.
38-
*/
39-
static getManagerForTrace(traceIndex?: number): SyntheticEventsManager {
40-
const last = syntheticEventsManagerByTraceIndex.at(-1);
41-
if (!last) {
42-
throw new Error('Attempted to get a SyntheticEventsManager without initializing');
43-
}
44-
if (traceIndex === undefined) {
45-
return last;
46-
}
47-
const manager = syntheticEventsManagerByTraceIndex.at(traceIndex);
48-
if (!manager) {
49-
throw new Error(`Attempted to get a SyntheticEventsManager with an invalid index ${traceIndex}`);
32+
static initAndActivate(rawEvents: readonly Types.TraceEvents.TraceEventData[]): SyntheticEventsManager {
33+
const existingManager = managerByRawEvents.get(rawEvents);
34+
if (existingManager) {
35+
activeManager = existingManager;
36+
} else {
37+
const manager = new SyntheticEventsManager(rawEvents);
38+
managerByRawEvents.set(rawEvents, manager);
39+
activeManager = manager;
5040
}
51-
return manager;
41+
return activeManager;
5242
}
5343

5444
static getActiveManager(): SyntheticEventsManager {
55-
const last = syntheticEventsManagerByTraceIndex.at(-1);
56-
if (!last) {
45+
if (!activeManager) {
5746
throw new Error('Attempted to get a SyntheticEventsManager without initializing');
5847
}
59-
return SyntheticEventsManager.getManagerForTrace(syntheticEventsManagerByTraceIndex.length - 1);
48+
return activeManager;
6049
}
6150

6251
static reset(): void {
6352
syntheticEventsManagerByTraceIndex.length = 0;
53+
activeManager = null;
6454
}
6555

6656
static registerSyntheticBasedEvent<T extends Types.TraceEvents.SyntheticBasedEvent>(syntheticEvent: Omit<T, '_tag'>):

‎front_end/models/trace/lantern/core/BUILD.gn‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ ts_library("unittests") {
3333
sources = [ "NetworkAnalyzer.test.ts" ]
3434

3535
deps = [
36-
"../:bundle",
36+
"../:bundle", # Lantern bundle
37+
"../../:bundle", # Trace engine bundle
3738
"../testing:bundle",
3839
]
3940
}

‎front_end/models/trace/lantern/simulation/BUILD.gn‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ ts_library("unittests") {
4646

4747
deps = [
4848
"../:bundle",
49+
"../../:bundle", # Trace engine bundle
4950
"../testing:bundle",
5051
]
5152
}

‎front_end/models/trace/lantern/testing/BUILD.gn‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ devtools_module("testing") {
1212

1313
deps = [
1414
"../:bundle",
15+
"../../:bundle",
1516
"../../../../testing",
1617
]
1718
}

‎front_end/panels/timeline/EventsSerializer.test.ts‎

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
// found in the LICENSE file.
44

55
import * as TraceEngine from '../../models/trace/trace.js';
6-
import * as Timeline from './timeline.js';
7-
86
import {getMainThread} from '../../testing/TraceHelpers.js';
97
import {TraceLoader} from '../../testing/TraceLoader.js';
108

9+
import * as Timeline from './timeline.js';
10+
1111
function findFirstEntry(
1212
allEntries: readonly TraceEngine.Types.TraceEvents.SyntheticTraceEntry[],
1313
predicate: (entry: TraceEngine.Types.TraceEvents.SyntheticTraceEntry) =>
@@ -19,15 +19,11 @@ function findFirstEntry(
1919
return entry;
2020
}
2121

22-
describe('ModificationsManager', () => {
22+
describe('EventsSerializer', () => {
2323
it('correctly implements a bidirectional key <-> event mapping', async function() {
24-
const data = await TraceLoader.traceEngine(null, 'basic-stack.json.gz');
24+
const data = await TraceLoader.traceEngine(this, 'basic-stack.json.gz');
2525
const eventsSerializer = new Timeline.EventsSerializer.EventsSerializer();
26-
if (!eventsSerializer) {
27-
throw new Error('Modifications manager does not exist.');
28-
}
2926
const mainThread = getMainThread(data.Renderer);
30-
assert.exists(eventsSerializer);
3127
// Find first 'Timer Fired' entry in the trace
3228
const rawEntry = findFirstEntry(mainThread.entries, entry => {
3329
return entry.name === 'TimerFire';

‎front_end/testing/BUILD.gn‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ ts_library("testing") {
6666
"../models/text_utils:bundle",
6767
"../models/timeline_model:bundle",
6868
"../models/trace:bundle",
69-
"../models/trace:bundle",
7069
"../models/workspace:bundle",
7170
"../panels/elements:meta",
7271
"../panels/sensors:meta",

‎front_end/testing/TraceLoader.ts‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export class TraceLoader {
9595
const contents = await TraceLoader.fixtureContents(context, name);
9696

9797
const events = 'traceEvents' in contents ? contents.traceEvents : contents;
98-
TraceEngine.Helpers.SyntheticEvents.SyntheticEventsManager.initSyntheticEventsManagerForTrace(events);
98+
TraceEngine.Helpers.SyntheticEvents.SyntheticEventsManager.initAndActivate(events);
9999
return events;
100100
}
101101

@@ -144,6 +144,19 @@ export class TraceLoader {
144144
if (fromCache) {
145145
TraceLoader.initTraceBoundsManager(fromCache.traceParsedData);
146146
Timeline.ModificationsManager.ModificationsManager.initAndActivateModificationsManager(fromCache.model, 0);
147+
148+
// This init step is usually done in model.parse(), but as we loaded from
149+
// the cache here, we manually run it.
150+
// The SyntheticEventsManager caches instances based on the rawEvents()
151+
// array, so we can safely do this even if we have already created an
152+
// instance for this trace before - the old one will be re-used, rather
153+
// than creating a new one.
154+
const rawEvents = fromCache.model.rawTraceEvents();
155+
if (rawEvents) {
156+
TraceEngine.Helpers.SyntheticEvents.SyntheticEventsManager.initAndActivate(
157+
rawEvents,
158+
);
159+
}
147160
return fromCache.traceParsedData;
148161
}
149162
const fileContents = await TraceLoader.fixtureContents(context, name);
@@ -158,7 +171,6 @@ export class TraceLoader {
158171

159172
TraceLoader.initTraceBoundsManager(traceEngineData.traceParsedData);
160173
Timeline.ModificationsManager.ModificationsManager.initAndActivateModificationsManager(traceEngineData.model, 0);
161-
162174
return traceEngineData.traceParsedData;
163175
}
164176

0 commit comments

Comments
 (0)