blob: eaa41e9ccefb81c7e19c7a20afdd77c7848b2c3c [file]
import { expect } from 'chai';
import { computeSuggestions } from './suggestion_engine';
import { Param, Query } from './worker-types';
describe('suggestion_engine memory-lookup path', () => {
const testParams: Param[] = [
{ id: 1, key: 'cpu', value: 'x86' },
{ id: 2, key: 'mode', value: 'release' },
{ id: 3, key: 'os', value: 'linux' },
{ id: 4, key: 'cpu', value: 'arm64' },
{ id: 5, key: 'mode', value: 'debug' },
];
const dummyShouldAbort = () => false;
it('should suggest a single attribute (sanity check)', async () => {
const queryInput = 'x86';
const currentQuery: Query = {};
const suggestions = await computeSuggestions(
queryInput,
currentQuery,
testParams,
null, // availableParams
null, // traceData
dummyShouldAbort
);
expect(suggestions).to.not.be.null;
expect(suggestions!.length).to.be.greaterThan(0);
expect(suggestions![0].params.length).to.equal(1);
expect(suggestions![0].params[0]).to.deep.equal({ id: 1, key: 'cpu', value: 'x86' });
});
it('should suggest value-only glob matches correctly', async () => {
const queryInput = 'x8*';
const currentQuery: Query = {};
const suggestions = await computeSuggestions(
queryInput,
currentQuery,
testParams,
null,
null,
dummyShouldAbort
);
expect(suggestions).to.not.be.null;
expect(suggestions!.length).to.equal(1);
expect(suggestions![0].params[0]).to.deep.equal({ id: -1, key: 'cpu', value: 'x8*' });
});
it('should suggest combinations and exclude single parameters when combinations exist', async () => {
const queryInput = 'linu deb';
const currentQuery: Query = {};
const suggestions = await computeSuggestions(
queryInput,
currentQuery,
testParams,
null,
null,
dummyShouldAbort
);
expect(suggestions).to.not.be.null;
suggestions!.forEach((s) => {
expect(s.params.length).to.be.greaterThan(1);
});
});
it('should suggest combined suggestions (Cartesian product) for space-separated queries', async () => {
const queryInput = 'linu deb';
const currentQuery: Query = {};
const suggestions = await computeSuggestions(
queryInput,
currentQuery,
testParams,
null,
null,
dummyShouldAbort
);
expect(suggestions).to.not.be.null;
const combined = suggestions!.find((s) => s.params.length > 1);
expect(combined).to.not.be.undefined;
expect(combined!.params.map((p) => p.value)).to.include('linux');
expect(combined!.params.map((p) => p.value)).to.include('debug');
});
it('should suggest combinations and compute exact intersection counts with traceData', async () => {
const queryInput = 'linu deb';
const currentQuery: Query = {};
// 1-based IDs in paramSets:
// Trace 0: cpu=x86 (id=1), mode=release (id=2), os=linux (id=3) -> [1, 2, 3, 0] (stride=4)
// Trace 1: cpu=arm64 (id=4), mode=debug (id=5), os=linux (id=3) -> [4, 5, 3, 0] (stride=4)
// Trace 2: cpu=x86 (id=1), mode=debug (id=5), os=linux (id=3) -> [1, 5, 3, 0] (stride=4)
// Param IDs:
// cpu=x86: 1
// mode=release: 2
// os=linux: 3
// cpu=arm64: 4
// mode=debug: 5
const paramSets = new Uint16Array([1, 2, 3, 0, 4, 5, 3, 0, 1, 5, 3, 0]);
const matchingParams = new Int32Array(100);
// Setup matchingParams so all parameters are considered matched for pool filtering
matchingParams.fill(1);
matchingParams[3] = 3; // os=linux (id=3) is in 3 traces
matchingParams[5] = 2; // mode=debug (id=5) is in 2 traces
const mockTraceData = {
paramSets,
stride: 4,
numTraces: 3,
bitsetSize: 10,
matchingParams,
} as any;
const suggestions = await computeSuggestions(
queryInput,
currentQuery,
testParams,
null,
mockTraceData,
dummyShouldAbort
);
expect(suggestions).to.not.be.null;
// The combination: os=linux (id 3) and mode=debug (id 5)
// Matching traces for combination: Trace 1 and Trace 2 (both have linux and debug). So count should be 2!
const combined = suggestions!.find((s) => s.params.length > 1);
expect(combined).to.not.be.undefined;
expect(combined!.count).to.equal(2);
});
it('should ignore virtual/non-existent query keys (like stat) and still match combinations correctly', async () => {
const queryInput = 'linu deb';
// currentQuery contains a virtual key 'stat'
const currentQuery: Query = {
stat: ['max', 'min'],
};
const paramSets = new Uint16Array([1, 2, 3, 0, 4, 5, 3, 0, 1, 5, 3, 0]);
const matchingParams = new Int32Array(100);
matchingParams.fill(1);
matchingParams[3] = 3; // os=linux (id=3) is in 3 traces
matchingParams[5] = 2; // mode=debug (id=5) is in 2 traces
const mockTraceData = {
paramSets,
stride: 4,
numTraces: 3,
bitsetSize: 10,
matchingParams,
} as any;
const suggestions = await computeSuggestions(
queryInput,
currentQuery,
testParams,
null,
mockTraceData,
dummyShouldAbort
);
expect(suggestions).to.not.be.null;
// Should still find the combination of os=linux (id 3) and mode=debug (id 5) with count 2, ignoring stat!
const combined = suggestions!.find((s) => s.params.length > 1);
expect(combined).to.not.be.undefined;
expect(combined!.count).to.equal(2);
});
it('should suggest other values for a key when key is in currentQuery, omitting only exact active key=value pair', async () => {
const params: Param[] = [
{ id: 1, key: 'os', value: 'linux' },
{ id: 2, key: 'os', value: 'linux_official' },
{ id: 3, key: 'os', value: 'windows' },
];
const queryInput = 'linu';
const currentQuery: Query = {
os: ['linux'],
};
const suggestions = await computeSuggestions(
queryInput,
currentQuery,
params,
null,
null,
dummyShouldAbort
);
expect(suggestions).to.not.be.null;
expect(suggestions!.length).to.equal(1);
expect(suggestions![0].params[0]).to.deep.equal({ id: 2, key: 'os', value: 'linux_official' });
});
it('should rank Android fully qualified class names correctly for Compose search (b/531657261)', async () => {
const androidParams: Param[] = [
{ id: 1, key: 'stat', value: 'value' },
{ id: 2, key: 'unit', value: 'ms' },
{ id: 3, key: 'test_class', value: 'androidx.compose.runtime.ComposeBenchmark' },
{
id: 4,
key: 'test_class',
value: 'androidx.compose.foundation.benchmark.LazyListScrollingBenchmark',
},
];
const suggestions = await computeSuggestions(
'Compose',
{},
androidParams,
null,
null,
dummyShouldAbort
);
expect(suggestions).to.not.be.null;
expect(suggestions!.length).to.be.greaterThan(0);
const topVal = suggestions![0].params[0].value;
expect(topVal).to.equal('androidx.compose.runtime.ComposeBenchmark');
});
it('should suggest exact class name for test_class=ComposeBenchmark (b/531657261)', async () => {
const androidParams: Param[] = [
{ id: 1, key: 'test_class', value: 'androidx.compose.runtime.ComposeBenchmark' },
{ id: 2, key: 'test_class', value: 'androidx.compose.foundation.CanvasTest' },
];
const suggestions = await computeSuggestions(
'test_class=ComposeBenchmark',
{},
androidParams,
null,
null,
dummyShouldAbort
);
expect(suggestions).to.not.be.null;
expect(suggestions!.length).to.be.greaterThan(0);
expect(suggestions![0].params[0].value).to.equal('androidx.compose.runtime.ComposeBenchmark');
});
it('should rank suggestions based on includeParams priority', async () => {
const params: Param[] = [
{ id: 1, key: 'arch', value: 'arm64' },
{ id: 2, key: 'os', value: 'android' },
];
// Query 'a' matches both 'arm64' (arch) and 'android' (os).
// With includeParams = ['os', 'arch'], 'os' (android) should rank higher.
const suggestions1 = await computeSuggestions(
'a',
{},
params,
null,
null,
dummyShouldAbort,
null,
['os', 'arch']
);
expect(suggestions1).to.not.be.null;
expect(suggestions1![0].params[0].key).to.equal('os');
// With includeParams = ['arch', 'os'], 'arch' (arm64) should rank higher.
const suggestions2 = await computeSuggestions(
'a',
{},
params,
null,
null,
dummyShouldAbort,
null,
['arch', 'os']
);
expect(suggestions2).to.not.be.null;
expect(suggestions2![0].params[0].key).to.equal('arch');
});
});