sort-test-expectations.mjs (4447B)
1 /** 2 * @license 3 * Copyright 2023 Google Inc. 4 * SPDX-License-Identifier: Apache-2.0 5 */ 6 7 // TODO: this could be an eslint rule probably. 8 import fs from 'fs'; 9 import path from 'path'; 10 import url from 'url'; 11 12 import prettier from 'prettier'; 13 14 const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); 15 const source = 'test/TestExpectations.json'; 16 let testExpectations = JSON.parse(fs.readFileSync(source, 'utf-8')); 17 const committedExpectations = structuredClone(testExpectations); 18 19 function testIdMatchesExpectationPattern(title, pattern) { 20 const patternRegExString = pattern 21 // Replace `*` with non special character 22 .replace(/\*/g, '--STAR--') 23 // Escape special characters https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping 24 .replace(/[.*+?^${}()|[\]\\]/g, '\\$&') 25 // Replace placeholder with greedy match 26 .replace(/--STAR--/g, '(.*)?'); 27 // Match beginning and end explicitly 28 const patternRegEx = new RegExp(`^${patternRegExString}$`); 29 return patternRegEx.test(title); 30 } 31 32 const prettierConfig = await import( 33 path.join(__dirname, '..', '.prettierrc.cjs') 34 ); 35 36 function getSpecificity(item) { 37 return ( 38 item.parameters.length + 39 (item.testIdPattern.includes('*') 40 ? item.testIdPattern === '*' 41 ? 0 42 : 1 43 : 2) 44 ); 45 } 46 47 testExpectations.sort((a, b) => { 48 const result = getSpecificity(a) - getSpecificity(b); 49 if (result === 0) { 50 return a.testIdPattern.localeCompare(b.testIdPattern); 51 } 52 return result; 53 }); 54 55 testExpectations.forEach(item => { 56 item.parameters.sort(); 57 item.expectations.sort(); 58 item.platforms.sort(); 59 // Delete comments for PASS expectations. They are likely outdated. 60 if (item.expectations.length === 1 && item.expectations[0] === 'PASS') { 61 delete item.comment; 62 } 63 }); 64 65 function isSubset(superset, subset) { 66 let isSubset = true; 67 68 for (const p of subset) { 69 if (!superset.has(p)) { 70 isSubset = false; 71 } 72 } 73 74 return isSubset; 75 } 76 77 const toBeRemoved = new Set(); 78 for (let i = testExpectations.length - 1; i >= 0; i--) { 79 const expectation = testExpectations[i]; 80 const params = new Set(expectation.parameters); 81 const expectations = new Set(expectation.expectations); 82 const platforms = new Set(expectation.platforms); 83 84 if (params.has('cdp') && params.has('firefox')) { 85 console.log( 86 'removing', 87 expectation, 88 'because firefox-cdp is no longer tested', 89 ); 90 toBeRemoved.add(expectation); 91 continue; 92 } 93 94 let foundMatch = false; 95 for (let j = i - 1; j >= 0; j--) { 96 const candidate = testExpectations[j]; 97 const candidateParams = new Set(candidate.parameters); 98 const candidateExpectations = new Set(candidate.expectations); 99 const candidatePlatforms = new Set(candidate.platforms); 100 101 if ( 102 testIdMatchesExpectationPattern( 103 expectation.testIdPattern, 104 candidate.testIdPattern, 105 ) && 106 isSubset(candidatePlatforms, platforms) && 107 (isSubset(params, candidateParams) || isSubset(candidateParams, params)) 108 ) { 109 foundMatch = true; 110 if (isSubset(candidateExpectations, expectations)) { 111 console.log('removing', expectation, 'already covered by', candidate); 112 toBeRemoved.add(expectation); 113 } 114 break; 115 } 116 } 117 118 if (!foundMatch && isSubset(new Set(['PASS']), expectations)) { 119 console.log( 120 'removing', 121 expectation, 122 'because the default expectation is to pass', 123 ); 124 toBeRemoved.add(expectation); 125 } 126 } 127 128 testExpectations = testExpectations.filter(item => { 129 return !toBeRemoved.has(item); 130 }); 131 132 if (process.argv.includes('--lint')) { 133 const missingComments = []; 134 testExpectations.forEach(item => { 135 if (item.expectations.length === 1 && item.expectations[0] === 'PASS') { 136 return; 137 } 138 if (!item.comment) { 139 missingComments.push(item); 140 } 141 }); 142 143 if ( 144 JSON.stringify(committedExpectations) !== JSON.stringify(testExpectations) 145 ) { 146 console.error( 147 `${source} is not formatted properly. Run 'npm run format:expectations'.`, 148 ); 149 process.exit(1); 150 } 151 152 if (missingComments.length > 0) { 153 console.error( 154 `${source}: missing comments for the following expectations:`, 155 missingComments, 156 ); 157 process.exit(1); 158 } 159 } else { 160 fs.writeFileSync( 161 source, 162 await prettier.format(JSON.stringify(testExpectations), { 163 ...prettierConfig, 164 parser: 'json', 165 }), 166 ); 167 }