blob: 202bd7b285f050dc778c76aa97584b257c06d176 [file]
// 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()));
}