| // Copyright 2021 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import {getDateFromTimestamp} from './buildbucket-utils'; |
| import {Build} from './checks-fetcher'; |
| import {Artifact, TestResult, TestVariant} from './resultdb-client'; |
| |
| // TODO(gavinmak): Make a resultdb-utils test. |
| |
| // Neither encodeURI() nor encodeURIComponent() encode parentheses. escape() |
| // does encode parentheses, but its usage is discouraged. URL_ESCAPES is used |
| // as an alternative to all the above. |
| const URL_ESCAPES: {[key: string]: string} = { |
| '(': '%28', |
| ')': '%29', |
| }; |
| const URL_ESCAPE_REGEX = new RegExp( |
| `[${Object.keys(URL_ESCAPES).map(c => `\\${c}`).join('')}]`, 'g'); |
| |
| /** |
| * Returns a link to the Web Test Result viewer. |
| * |
| * @param variant Test variant. |
| * @param build Buildbucket build. |
| */ |
| export function createLayoutResultLink(variant: TestVariant, build: Build): string { |
| if (!/^ninja:\/\/:blink_web_tests\//.test(variant.testId!) || |
| !build.number) { |
| return ''; |
| } |
| |
| let stepName = variant.results[0].result |
| .tags?.find(t => t['key'] === 'step_name')?.['value']; |
| if (!stepName) { |
| return ''; |
| } |
| |
| // Remove everything past the first ')' and encode special characters, ex: |
| // 'test (patch) on some OS' becomes 'test%20%28patch%29' |
| stepName = /^([^\)]*\)?)/.exec(stepName)![1]; |
| stepName = encodeURIComponent(stepName); |
| stepName = stepName.replaceAll(URL_ESCAPE_REGEX, m => URL_ESCAPES[m]); |
| |
| return `https://test-results.appspot.com/data/layout_results/` + |
| `${encodeURIComponent(build.builder.builder!)}/${build.number}/` + |
| `${stepName}/layout-test-results/results.html`; |
| } |
| |
| /** |
| * Returns a deep link to a specific TestVariant. |
| * |
| * @param variant Test variant. |
| * @param build Buildbucket build. |
| */ |
| export function createVariantLink(variant: TestVariant, build: Build): string { |
| const builder = build.builder; |
| return encodeURI(`https://ci.chromium.org/ui/p/${builder.project}/` + |
| `builders/${builder.bucket}/` + |
| `${encodeURIComponent(builder.builder!)}/`) + |
| `b${build.id}/test-results?q=ExactID%3A` + |
| `${encodeURIComponent(variant.testId!)}+VHash%3A` + |
| `${encodeURIComponent(variant.variantHash!)}&clean=`; |
| } |
| |
| /** |
| * Returns a link to the Swarming test task. |
| * |
| * @param testResult Test result. |
| */ |
| export function createTestTaskLink(testResult: TestResult): string { |
| const match = |
| /^invocations\/task-([a-zA-Z\-\.]+\.com)-(\w+)/.exec(testResult.name); |
| if (match) { |
| return encodeURI(`https://${match[1]}/task?id=${match[2]}`); |
| } |
| |
| // If the regex doesn't match, check tags for the swarming task ID. |
| const taskId = testResult.tags?.find(t => t['key'] === 'swarming_task_id')?.['value']; |
| if (taskId) { |
| return `https://chromium-swarm.appspot.com/task?id=${taskId}`; |
| } |
| return ''; |
| } |
| |
| /** |
| * Returns an appropriate human-readable variant name. |
| * |
| * @param variant Test variant. |
| */ |
| export function createVariantName(variant: TestVariant): string { |
| return variant.testMetadata?.name || variant.testId; |
| } |
| |
| /** |
| * Returns the parent invocation from a TestResult's name. |
| * |
| * @param resultName The result name. |
| */ |
| export function getParentInv(resultName: string): string { |
| const match = /^(invocations\/.+?)\//.exec(resultName); |
| return (match && match[1]) ?? ''; |
| } |
| |
| /** |
| * Returns the earliest fetchUrlExpiration in a list of artifacts. The |
| * returned fetchUrlExpiration is represented as the number of milliseconds |
| * since the Unix Epoch. |
| */ |
| export function getEarliestArtifactExpiration(artifacts: Artifact[]): number { |
| return Math.min(...artifacts |
| .map(a => getDateFromTimestamp(a.fetchUrlExpiration)!.getTime())); |
| } |