| // 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. |
| |
| /** |
| * This is written in JS to be able to use it in Presubmit.py without a build. |
| */ |
| |
| /** |
| * @typedef {('Failure'|'Pass'|'Skip')} ExpectationResult |
| */ |
| |
| /** |
| * @typedef {Object} Expectation |
| * @property {string} line |
| * @property {boolean=} isCommentOrEmpty |
| * @property {string[]=} bugs |
| * @property {string[]=} platforms |
| * @property {string=} testName |
| * @property {ExpectationResult[]=} results |
| */ |
| |
| // bug: required, must start with crbug.com/ followed by numbers |
| const bugPattern = '(crbug\\.com\\/\\d+)\\s+'; |
| // platforms: optional, space-separated platform names in brackets e.g. [ mac linux win32 ] |
| const platformsPattern = '(?:\\[([^\\]]*)\\]\\s+)?'; |
| // testName: required, a string |
| const testNamePattern = '([^\\s\\[]+)\\s+'; |
| // results: required, space-separated results in brackets e.g. [ Failure ] |
| const resultsPattern = '\\[([^\\]]+)\\]'; |
| const EXPECTATION_REGEX = new RegExp(`^${bugPattern}${platformsPattern}${testNamePattern}${resultsPattern}$`); |
| |
| /** |
| * @param {string} content |
| * @returns {Expectation[]} |
| */ |
| export function parseExpectations(content) { |
| const lines = content.split('\n'); |
| return lines.map(line => parseExpectationLine(line)); |
| } |
| |
| /** |
| * @param {string} line |
| * @returns {Expectation} |
| */ |
| export function parseExpectationLine(line) { |
| const trimmed = line.trim(); |
| if (!trimmed || trimmed.startsWith('#')) { |
| return {line, isCommentOrEmpty: true}; |
| } |
| |
| const match = trimmed.match(EXPECTATION_REGEX); |
| if (match) { |
| const bugStr = match[1] ? match[1].trim() : ''; |
| const bugs = bugStr ? [bugStr] : []; |
| if (bugs.length === 0) { |
| throw new Error(`Expectation must have at least one bug: ${line}`); |
| } |
| |
| const platformsStr = match[2] ? match[2].trim() : ''; |
| const platforms = platformsStr ? platformsStr.split(/\s+/) : []; |
| const validPlatforms = new Set(['mac', 'linux', 'win32']); |
| for (const p of platforms) { |
| if (!validPlatforms.has(p)) { |
| throw new Error(`Invalid platform '${p}' in expectation: ${line}`); |
| } |
| } |
| |
| const resultsStr = match[4] ? match[4].trim() : ''; |
| const results = /** @type {ExpectationResult[]} */ (resultsStr ? resultsStr.split(/\s+/) : []); |
| const validResults = new Set(['Failure', 'Pass', 'Skip']); |
| for (const r of results) { |
| if (!validResults.has(r)) { |
| throw new Error(`Invalid result '${r}' in expectation: ${line}`); |
| } |
| } |
| |
| return { |
| line, |
| isCommentOrEmpty: false, |
| bugs, |
| platforms, |
| testName: match[3], |
| results, |
| }; |
| } |
| |
| throw new Error(`Could not parse expectation line: ${line}`); |
| } |
| |
| /** |
| * @param {Expectation[]} expectations |
| * @returns {string} |
| */ |
| export function serializeExpectations(expectations) { |
| return expectations.map(serializeExpectationLine).join('\n'); |
| } |
| |
| /** |
| * @param {Expectation} expectation |
| * @returns {string} |
| */ |
| export function serializeExpectationLine(expectation) { |
| if (expectation.isCommentOrEmpty) { |
| return expectation.line; |
| } |
| |
| const parts = []; |
| if (expectation.bugs && expectation.bugs.length > 0) { |
| parts.push(expectation.bugs.join(' ')); |
| } |
| if (expectation.platforms && expectation.platforms.length > 0) { |
| const sortedPlatforms = [...expectation.platforms].sort(); |
| parts.push(`[ ${sortedPlatforms.join(' ')} ]`); |
| } |
| if (expectation.testName) { |
| parts.push(expectation.testName); |
| } |
| if (expectation.results && expectation.results.length > 0) { |
| const sortedResults = [...expectation.results].sort(); |
| parts.push(`[ ${sortedResults.join(' ')} ]`); |
| } |
| |
| return parts.join(' '); |
| } |