| // Copyright 2026 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| const replacements: Record<string, string|undefined> = { |
| '\n': '_lf_', |
| '\r': '_cr_', |
| '\t': '_tab_', |
| '\x00': '_null_', |
| '\x07': '_bell_', |
| }; |
| |
| const nonPrintRegex = /[^\x20-\x7E]/ug; |
| |
| function replaceNonPrintable(str: string) { |
| return str.replace(nonPrintRegex, match => { |
| return replacements[match] !== undefined ? replacements[match] : ''; |
| }); |
| } |
| |
| export function escapeTestIdBlock(block: string): string { |
| return replaceNonPrintable(block.toLowerCase()).replace(/"/g, '_dblquote_').replace(/\s+/g, '_').replace(/:/g, '_'); |
| } |
| |
| /** |
| * Build test ID is like the test ID used on the CLI but the path part of it is |
| * an absolute path to the build dir. |
| */ |
| export function computeBuildTestId(file: string, titlePath: string[]) { |
| const blocks = titlePath.map(escapeTestIdBlock); |
| const caseName = blocks.join(':'); |
| const exactTestId = `${file}:${caseName}`; |
| return exactTestId; |
| } |
| |
| export function generateExactTestId( |
| genDir: string, |
| file: string, |
| titlePath: string[], |
| ): { |
| exactTestId: string, |
| coarseName: string, |
| fineName: string, |
| caseName: string, |
| } { |
| const blocks = titlePath.map(escapeTestIdBlock); |
| const caseName = blocks.join(':'); |
| |
| const normalizedGenDir = genDir.replace(/\\/g, '/'); |
| const normalizedFile = file.replace(/\\/g, '/'); |
| |
| let relativeSourceFileName = normalizedFile; |
| if (normalizedFile.startsWith(normalizedGenDir)) { |
| relativeSourceFileName = normalizedFile.substring(normalizedGenDir.length); |
| if (relativeSourceFileName.startsWith('/')) { |
| relativeSourceFileName = relativeSourceFileName.substring(1); |
| } |
| } |
| |
| relativeSourceFileName = relativeSourceFileName.replace(/\.js$/, '.ts'); |
| |
| const parsedPath = relativeSourceFileName.split('/'); |
| const fineName = parsedPath.pop() || ''; |
| const coarseName = parsedPath.length > 0 ? `${parsedPath.join('/')}/` : ''; |
| const exactTestId = `${relativeSourceFileName}:${caseName}`; |
| if (exactTestId.length >= 512) { |
| throw new Error('Test ID is too long'); |
| } |
| return {exactTestId, coarseName, fineName, caseName}; |
| } |
| |
| export function formatFailedTestsSummary(failedTestIds: Iterable<string>): string { |
| const ids = Array.from(failedTestIds); |
| if (ids.length === 0) { |
| return ''; |
| } |
| return `\nFailed tests (${ids.length}):\n${ids.map(id => ` ${id}`).join('\n')}\n\nTo rerun:\n npm run test -- ${ |
| ids.join(' ')}\n\n`; |
| } |