compare.ts (3528B)
1 import { TestParams } from '../../framework/fixture.js'; 2 import { assert, objectEquals } from '../../util/util.js'; 3 import { paramKeyIsPublic } from '../params_utils.js'; 4 5 import { TestQuery } from './query.js'; 6 7 export const enum Ordering { 8 Unordered, 9 StrictSuperset, 10 Equal, 11 StrictSubset, 12 } 13 14 /** 15 * Compares two queries for their ordering (which is used to build the tree). 16 * 17 * See src/unittests/query_compare.spec.ts for examples. 18 */ 19 export function compareQueries(a: TestQuery, b: TestQuery): Ordering { 20 if (a.suite !== b.suite) { 21 return Ordering.Unordered; 22 } 23 24 const filePathOrdering = comparePaths(a.filePathParts, b.filePathParts); 25 if (filePathOrdering !== Ordering.Equal || a.isMultiFile || b.isMultiFile) { 26 return compareOneLevel(filePathOrdering, a.isMultiFile, b.isMultiFile); 27 } 28 assert('testPathParts' in a && 'testPathParts' in b); 29 30 const testPathOrdering = comparePaths(a.testPathParts, b.testPathParts); 31 if (testPathOrdering !== Ordering.Equal || a.isMultiTest || b.isMultiTest) { 32 return compareOneLevel(testPathOrdering, a.isMultiTest, b.isMultiTest); 33 } 34 assert('params' in a && 'params' in b); 35 36 const paramsPathOrdering = comparePublicParamsPaths(a.params, b.params); 37 if (paramsPathOrdering !== Ordering.Equal || a.isMultiCase || b.isMultiCase) { 38 return compareOneLevel(paramsPathOrdering, a.isMultiCase, b.isMultiCase); 39 } 40 return Ordering.Equal; 41 } 42 43 /** 44 * Compares a single level of a query. 45 * 46 * "IsBig" means the query is big relative to the level, e.g. for test-level: 47 * - Anything >= `suite:a,*` is big 48 * - Anything <= `suite:a:*` is small 49 */ 50 function compareOneLevel(ordering: Ordering, aIsBig: boolean, bIsBig: boolean): Ordering { 51 assert(ordering !== Ordering.Equal || aIsBig || bIsBig); 52 if (ordering === Ordering.Unordered) return Ordering.Unordered; 53 if (aIsBig && bIsBig) return ordering; 54 if (!aIsBig && !bIsBig) return Ordering.Unordered; // Equal case is already handled 55 // Exactly one of (a, b) is big. 56 if (aIsBig && ordering !== Ordering.StrictSubset) return Ordering.StrictSuperset; 57 if (bIsBig && ordering !== Ordering.StrictSuperset) return Ordering.StrictSubset; 58 return Ordering.Unordered; 59 } 60 61 /** 62 * Compare two file paths, or file-local test paths, returning an Ordering between the two. 63 */ 64 export function comparePaths(a: readonly string[], b: readonly string[]): Ordering { 65 const shorter = Math.min(a.length, b.length); 66 67 for (let i = 0; i < shorter; ++i) { 68 if (a[i] !== b[i]) { 69 return Ordering.Unordered; 70 } 71 } 72 if (a.length === b.length) { 73 return Ordering.Equal; 74 } else if (a.length < b.length) { 75 return Ordering.StrictSuperset; 76 } else { 77 return Ordering.StrictSubset; 78 } 79 } 80 81 export function comparePublicParamsPaths(a: TestParams, b: TestParams): Ordering { 82 const aKeys = Object.keys(a).filter(k => paramKeyIsPublic(k)); 83 const commonKeys = new Set(aKeys.filter(k => k in b)); 84 85 for (const k of commonKeys) { 86 // Treat +/-0.0 as different query by distinguishing them in objectEquals 87 if (!objectEquals(a[k], b[k], true)) { 88 return Ordering.Unordered; 89 } 90 } 91 const bKeys = Object.keys(b).filter(k => paramKeyIsPublic(k)); 92 const aRemainingKeys = aKeys.length - commonKeys.size; 93 const bRemainingKeys = bKeys.length - commonKeys.size; 94 if (aRemainingKeys === 0 && bRemainingKeys === 0) return Ordering.Equal; 95 if (aRemainingKeys === 0) return Ordering.StrictSuperset; 96 if (bRemainingKeys === 0) return Ordering.StrictSubset; 97 return Ordering.Unordered; 98 }