Remove constituents of TracesParser correctly.

When a TracesParser is created, remove the underlying parsers from LoadedParsers.
Also deduplicated async calls to get the same fixture files.

Bug: 319302757
Test: npm run test:unit:ci
Change-Id: Id08dfbf2581a6a1ce7d26c036798cbaa6d7cd4a3
diff --git a/tools/winscope/src/app/loaded_parsers.ts b/tools/winscope/src/app/loaded_parsers.ts
index 831fd4f..a998232 100644
--- a/tools/winscope/src/app/loaded_parsers.ts
+++ b/tools/winscope/src/app/loaded_parsers.ts
@@ -25,7 +25,7 @@
 import {Parser} from 'trace/parser';
 import {TraceFile} from 'trace/trace_file';
 import {TRACE_INFO} from 'trace/trace_info';
-import {TraceType} from 'trace/trace_type';
+import {TraceEntryTypeMap, TraceType} from 'trace/trace_type';
 
 export class LoadedParsers {
   static readonly MAX_ALLOWED_TIME_GAP_BETWEEN_TRACES_NS = BigInt(
@@ -72,7 +72,7 @@
     return fileAndParsers.map((fileAndParser) => fileAndParser.parser);
   }
 
-  remove(parser: Parser<object>) {
+  remove<T extends TraceType>(parser: Parser<TraceEntryTypeMap[T]>) {
     this.legacyParsers = this.legacyParsers.filter(
       (fileAndParser) => fileAndParser.parser !== parser,
     );
diff --git a/tools/winscope/src/app/trace_pipeline.ts b/tools/winscope/src/app/trace_pipeline.ts
index 4b73368..54604ae 100644
--- a/tools/winscope/src/app/trace_pipeline.ts
+++ b/tools/winscope/src/app/trace_pipeline.ts
@@ -31,7 +31,7 @@
 import {Trace} from 'trace/trace';
 import {Traces} from 'trace/traces';
 import {TraceFile} from 'trace/trace_file';
-import {TraceType, TraceTypeUtils} from 'trace/trace_type';
+import {TraceEntryTypeMap, TraceType, TraceTypeUtils} from 'trace/trace_type';
 import {FilesSource} from './files_source';
 import {LoadedParsers} from './loaded_parsers';
 import {TraceFileFilter} from './trace_file_filter';
@@ -89,27 +89,27 @@
       const hasTransitionTrace =
         this.traces.getTrace(TraceType.TRANSITION) !== undefined;
       if (hasTransitionTrace) {
-        this.traces.deleteTracesByType(TraceType.WM_TRANSITION);
-        this.traces.deleteTracesByType(TraceType.SHELL_TRANSITION);
+        this.removeTracesAndParsersByType(TraceType.WM_TRANSITION);
+        this.removeTracesAndParsersByType(TraceType.SHELL_TRANSITION);
       }
 
       const hasCujTrace = this.traces.getTrace(TraceType.CUJS) !== undefined;
       if (hasCujTrace) {
-        this.traces.deleteTracesByType(TraceType.EVENT_LOG);
+        this.removeTracesAndParsersByType(TraceType.EVENT_LOG);
       }
 
       const hasMergedInputTrace =
         this.traces.getTrace(TraceType.INPUT_EVENT_MERGED) !== undefined;
       if (hasMergedInputTrace) {
-        this.traces.deleteTracesByType(TraceType.INPUT_KEY_EVENT);
-        this.traces.deleteTracesByType(TraceType.INPUT_MOTION_EVENT);
+        this.removeTracesAndParsersByType(TraceType.INPUT_KEY_EVENT);
+        this.removeTracesAndParsersByType(TraceType.INPUT_MOTION_EVENT);
       }
     } finally {
       progressListener?.onOperationFinished(true);
     }
   }
 
-  removeTrace(trace: Trace<object>) {
+  removeTrace<T extends TraceType>(trace: Trace<TraceEntryTypeMap[T]>) {
     this.loadedParsers.remove(trace.getParser());
     this.traces.deleteTrace(trace);
   }
@@ -312,4 +312,11 @@
 
     return unzippedArchives;
   }
+
+  private removeTracesAndParsersByType(type: TraceType) {
+    const traces = this.traces.getTraces(type);
+    traces.forEach((trace) => {
+      this.removeTrace(trace);
+    });
+  }
 }
diff --git a/tools/winscope/src/app/trace_pipeline_test.ts b/tools/winscope/src/app/trace_pipeline_test.ts
index f3051e7..d385b8f 100644
--- a/tools/winscope/src/app/trace_pipeline_test.ts
+++ b/tools/winscope/src/app/trace_pipeline_test.ts
@@ -36,22 +36,56 @@
 describe('TracePipeline', () => {
   let validSfFile: File;
   let validWmFile: File;
+  let shellTransitionFile: File;
+  let wmTransitionFile: File;
+  let screenshotFile: File;
+  let screenRecordingFile: File;
+  let brMainEntryFile: File;
+  let brCodenameFile: File;
+  let brSfFile: File;
+  let jpgFile: File;
+
   let progressListener: ProgressListenerStub;
   let tracePipeline: TracePipeline;
   let userNotifierChecker: UserNotifierChecker;
 
-  beforeAll(() => {
+  beforeAll(async () => {
     userNotifierChecker = new UserNotifierChecker();
-  });
-
-  beforeEach(async () => {
-    jasmine.addCustomEqualityTester(UnitTestUtils.timestampEqualityTester);
+    wmTransitionFile = await UnitTestUtils.getFixtureFile(
+      'traces/elapsed_and_real_timestamp/wm_transition_trace.pb',
+    );
+    shellTransitionFile = await UnitTestUtils.getFixtureFile(
+      'traces/elapsed_and_real_timestamp/shell_transition_trace.pb',
+    );
     validSfFile = await UnitTestUtils.getFixtureFile(
       'traces/elapsed_and_real_timestamp/SurfaceFlinger.pb',
     );
     validWmFile = await UnitTestUtils.getFixtureFile(
       'traces/elapsed_and_real_timestamp/WindowManager.pb',
     );
+    screenshotFile = await UnitTestUtils.getFixtureFile(
+      'traces/screenshot.png',
+    );
+    screenRecordingFile = await UnitTestUtils.getFixtureFile(
+      'traces/elapsed_and_real_timestamp/screen_recording_metadata_v2.mp4',
+    );
+    brMainEntryFile = await UnitTestUtils.getFixtureFile(
+      'bugreports/main_entry.txt',
+      'main_entry.txt',
+    );
+    brCodenameFile = await UnitTestUtils.getFixtureFile(
+      'bugreports/bugreport-codename_beta-UPB2.230407.019-2023-05-30-14-33-48.txt',
+      'bugreport-codename_beta-UPB2.230407.019-2023-05-30-14-33-48.txt',
+    );
+    brSfFile = await UnitTestUtils.getFixtureFile(
+      'traces/elapsed_and_real_timestamp/SurfaceFlinger.pb',
+      'FS/data/misc/wmtrace/surface_flinger.bp',
+    );
+    jpgFile = await UnitTestUtils.getFixtureFile('winscope_homepage.jpg');
+  });
+
+  beforeEach(async () => {
+    jasmine.addCustomEqualityTester(UnitTestUtils.timestampEqualityTester);
 
     progressListener = new ProgressListenerStub();
     spyOn(progressListener, 'onProgressUpdate');
@@ -133,18 +167,9 @@
     expect(tracePipeline.getTraces().getSize()).toEqual(0);
 
     const bugreportFiles = [
-      await UnitTestUtils.getFixtureFile(
-        'bugreports/main_entry.txt',
-        'main_entry.txt',
-      ),
-      await UnitTestUtils.getFixtureFile(
-        'bugreports/bugreport-codename_beta-UPB2.230407.019-2023-05-30-14-33-48.txt',
-        'bugreport-codename_beta-UPB2.230407.019-2023-05-30-14-33-48.txt',
-      ),
-      await UnitTestUtils.getFixtureFile(
-        'traces/elapsed_and_real_timestamp/SurfaceFlinger.pb',
-        'FS/data/misc/wmtrace/surface_flinger.bp',
-      ),
+      brMainEntryFile,
+      brCodenameFile,
+      brSfFile,
       await UnitTestUtils.getFixtureFile(
         'traces/elapsed_and_real_timestamp/wm_transition_trace.pb',
         'FS/data/misc/ignored-dir/window_manager.bp',
@@ -177,20 +202,7 @@
   });
 
   it('detects bugreports and extracts timezone info, then calculates utc offset', async () => {
-    const bugreportFiles = [
-      await UnitTestUtils.getFixtureFile(
-        'bugreports/main_entry.txt',
-        'main_entry.txt',
-      ),
-      await UnitTestUtils.getFixtureFile(
-        'bugreports/bugreport-codename_beta-UPB2.230407.019-2023-05-30-14-33-48.txt',
-        'bugreport-codename_beta-UPB2.230407.019-2023-05-30-14-33-48.txt',
-      ),
-      await UnitTestUtils.getFixtureFile(
-        'traces/elapsed_and_real_timestamp/SurfaceFlinger.pb',
-        'FS/data/misc/wmtrace/surface_flinger.bp',
-      ),
-    ];
+    const bugreportFiles = [brMainEntryFile, brCodenameFile, brSfFile];
     const bugreportArchive = new File(
       [await FileUtils.createZipArchive(bugreportFiles)],
       'bugreport.zip',
@@ -226,10 +238,7 @@
   });
 
   it('is robust to invalid trace files', async () => {
-    const invalidFiles = [
-      await UnitTestUtils.getFixtureFile('winscope_homepage.jpg'),
-    ];
-
+    const invalidFiles = [jpgFile];
     await loadFiles(invalidFiles);
 
     await expectLoadResult(0, [
@@ -266,7 +275,7 @@
   it('is robust to mixed valid and invalid trace files', async () => {
     expect(tracePipeline.getTraces().getSize()).toEqual(0);
     const files = [
-      await UnitTestUtils.getFixtureFile('winscope_homepage.jpg'),
+      jpgFile,
       await UnitTestUtils.getFixtureFile('traces/dump_WindowManager.pb'),
     ];
 
@@ -295,6 +304,54 @@
     await expectLoadResult(0, []);
   });
 
+  it('removes constituent traces of transitions trace', async () => {
+    await loadFiles([wmTransitionFile, wmTransitionFile, shellTransitionFile]);
+    await expectLoadResult(1, []);
+
+    const transitionTrace = assertDefined(
+      tracePipeline.getTraces().getTrace(TraceType.TRANSITION),
+    );
+
+    tracePipeline.removeTrace(transitionTrace);
+    await expectLoadResult(0, []);
+
+    await loadFiles([wmTransitionFile]);
+    await expectLoadResult(1, []);
+    expect(
+      tracePipeline.getTraces().getTrace(TraceType.WM_TRANSITION),
+    ).toBeDefined();
+  });
+
+  it('removes constituent traces of CUJs trace', async () => {
+    const eventlogFile = await UnitTestUtils.getFixtureFile(
+      'traces/eventlog.winscope',
+    );
+    await loadFiles([eventlogFile]);
+    await expectLoadResult(1, []);
+
+    const cujTrace = assertDefined(
+      tracePipeline.getTraces().getTrace(TraceType.CUJS),
+    );
+
+    tracePipeline.removeTrace(cujTrace);
+    await expectLoadResult(0, []);
+  });
+
+  it('removes constituent traces of input trace', async () => {
+    const inputFile = await UnitTestUtils.getFixtureFile(
+      'traces/perfetto/input-events.perfetto-trace',
+    );
+    await loadFiles([inputFile]);
+    await expectLoadResult(1, []);
+
+    const inputTrace = assertDefined(
+      tracePipeline.getTraces().getTrace(TraceType.INPUT_EVENT_MERGED),
+    );
+
+    tracePipeline.removeTrace(inputTrace);
+    await expectLoadResult(0, []);
+  });
+
   it('gets loaded traces', async () => {
     await loadFiles([validSfFile, validWmFile]);
     await expectLoadResult(2, []);
@@ -313,11 +370,7 @@
   });
 
   it('gets screenrecording data', async () => {
-    const files = [
-      await UnitTestUtils.getFixtureFile(
-        'traces/elapsed_and_real_timestamp/screen_recording_metadata_v2.mp4',
-      ),
-    ];
+    const files = [screenRecordingFile];
     await loadFiles(files);
     await expectLoadResult(1, []);
 
@@ -327,7 +380,7 @@
   });
 
   it('gets screenshot data', async () => {
-    const files = [await UnitTestUtils.getFixtureFile('traces/screenshot.png')];
+    const files = [screenshotFile];
     await loadFiles(files);
     await expectLoadResult(1, []);
 
@@ -337,12 +390,7 @@
   });
 
   it('prioritises screenrecording over screenshot data', async () => {
-    const files = [
-      await UnitTestUtils.getFixtureFile('traces/screenshot.png'),
-      await UnitTestUtils.getFixtureFile(
-        'traces/elapsed_and_real_timestamp/screen_recording_metadata_v2.mp4',
-      ),
-    ];
+    const files = [screenshotFile, screenRecordingFile];
     await loadFiles(files);
     await expectLoadResult(1, [
       new TraceOverridden('screenshot.png', TraceType.SCREEN_RECORDING),
@@ -365,9 +413,7 @@
 
   it('creates zip archive with loaded trace files', async () => {
     const files = [
-      await UnitTestUtils.getFixtureFile(
-        'traces/elapsed_and_real_timestamp/screen_recording_metadata_v2.mp4',
-      ),
+      screenRecordingFile,
       await UnitTestUtils.getFixtureFile(
         'traces/perfetto/transactions_trace.perfetto-trace',
       ),
@@ -401,9 +447,6 @@
   });
 
   it('can filter traces without visualization', async () => {
-    const shellTransitionFile = await UnitTestUtils.getFixtureFile(
-      'traces/elapsed_and_real_timestamp/shell_transition_trace.pb',
-    );
     await loadFiles([validSfFile, shellTransitionFile]);
     await expectLoadResult(2, []);