Fix typescript lint issues Change-Id: Ib38b29e3f0b94890f66db55b79049554c9bd3bc6 Reviewed-on: https://chromium-review.googlesource.com/c/infra/gerrit-plugins/buildbucket/+/3556504 Reviewed-by: Josip Sokcevic <sokcevic@google.com> Reviewed-by: Gavin Mak <gavinmak@google.com> Commit-Queue: Aravind Vasudevan <aravindvasudev@google.com>
diff --git a/web/auth.ts b/web/auth.ts index 9a2b3bb..d01c127 100644 --- a/web/auth.ts +++ b/web/auth.ts
@@ -19,23 +19,23 @@ // Gerrit JWT Mendel feature flag export const GERRIT_JWT_FLAG = 'UiFeature__gerrit_jwt_token_buildbucket_plugin'; // exported for tests only -declare interface OAuthConfig { - auth_url?: string - proxy_url?: string - client_id?: string - email?: string +export declare interface OAuthConfig { + auth_url?: string; + proxy_url?: string; + client_id?: string; + email?: string; } -declare interface Token { - access_token?: string - signed_jwt?: string - expires_at: number - error?: string +export declare interface Token { + access_token?: string; + signed_jwt?: string; + expires_at: number | null; + error?: string; } export declare interface AuthorizationHeader { - 'X-Gerrit-Auth'?: string - authorization?: string + 'X-Gerrit-Auth'?: string; + authorization?: string; } /** @@ -92,7 +92,7 @@ export class Authenticator { config: OAuthConfig; plugin: PluginApi; - private readonly token: {[key: string]: Token}; + private readonly token: {[key: string]: (Token | null)}; private currentFetchTokenPromise: Promise<Token> | null; constructor(config: OAuthConfig, plugin: PluginApi) { @@ -119,7 +119,7 @@ console.debug('bb: Resolved _fetchToken()'); } - return this.token[changeId]; + return this.token[changeId]!; } /** @@ -151,7 +151,7 @@ * is not logged in. Rejects on an error or timeout. */ private async fetchTokenUnserialized(changeId: string, timeoutMs: number): Promise<Token> { - let token: Token = null; + let token: Token | null = null; // Check whether Gerrit JWT feature is enabled const experiments = window.ENABLED_EXPERIMENTS || []; @@ -199,7 +199,7 @@ * @param token A token object, see https://goo.gl/HZH4Nq. * @return True if valid. */ - private isValidToken(token: Token): boolean { + private isValidToken(token: Token | null): boolean { if (!token || !(token.access_token || token.signed_jwt)) { return false; } @@ -236,7 +236,7 @@ * @param platformJsElement Script element where platform.js * is loaded. */ -async function initAuthenticator(plugin: PluginApi, platformJsElement: HTMLScriptElement): Promise<Authenticator> { +async function initAuthenticator(plugin: PluginApi, platformJsElement: HTMLScriptElement): Promise<Authenticator | null> { // Cannot load OAuth config if the user is not logged in. console.debug('bb: Awaiting plugin.restApi().getLoggedIn()'); const loggedIn = await plugin.restApi().getLoggedIn();
diff --git a/web/buildbucket-client.ts b/web/buildbucket-client.ts index 4e285c7..cebf179 100644 --- a/web/buildbucket-client.ts +++ b/web/buildbucket-client.ts
@@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import {AuthorizationHeader, getAuthorizationHeader} from './auth'; +import {Build} from './checks-fetcher'; // Extra buildbucket tag to be used for collecting metrics // about use of the "retry failed" button. @@ -21,13 +22,14 @@ } export declare interface Builder { + id?: {builder?: string}; project?: string; builder?: string; bucket?: string; } -declare interface GerritChange { - host: string; +export declare interface GerritChange { + host?: string; change: number; project: string; patchset: number; @@ -37,7 +39,7 @@ requests: BuildRequest[]; } -export type BuildRequest = ScheduleBuildRequest | CancelBuildRequest | SearchBuildRequest | ListBuildersRequest; +export type BuildRequest = ScheduleBuildRequest | CancelBuildRequest | SearchBuildsRequest | ListBuildersRequest; export declare interface ScheduleBuildRequest { scheduleBuild: { @@ -57,22 +59,32 @@ }; } -export declare interface SearchBuildRequest { +export declare interface SearchBuildsRequest { searchBuilds: { pageSize: number; predicate: { includeExperimental: boolean; gerritChanges: GerritChange[]; + builder?: Builder; }; - mask: { + mask?: { fields: string; outputProperties: [ {[key: string]: string[]} ]; }; + pageToken?: number; } } +export declare interface SearchBuildsResponse { + searchBuilds: { + builds: Build[]; + nextPageToken: number; + }; + error: any; +} + export declare interface ListBuildersRequest { project: string; bucket: string; @@ -80,6 +92,11 @@ pageSize: number; } +export declare interface ListBuildersResponse { + builders: Builder[]; + nextPageToken: string; +} + /** * Constructs a batch request to schedule builds for a set of builders. * @@ -124,7 +141,7 @@ */ export async function retry(fn: () => any, timeout: number, maxTimeout: number, maxAttempts: number, exp: number): Promise<any> { - const sleep = (ms: number) => {new Promise(resolve => setTimeout(resolve, ms))}; + const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); let attempt = 0; while (attempt < maxAttempts) { try { @@ -166,7 +183,7 @@ * @param signal An AbortSignal object. * @return Response body. */ - private async call(service: string, method: string, request: BuildRequests | BuildRequest, signal: AbortSignal): Promise<any> { + private async call(service: string, method: string, request: BuildRequests | BuildRequest, signal: AbortSignal | undefined): Promise<any> { // Miniature implementation of pRPC protocol with JSONPB and auth. const authHeader = await this.getAuthorizationHeader(); const headers = { @@ -209,7 +226,7 @@ return this.call('buildbucket.v2.Builds', 'Batch', request, signal); } - searchBuilds(request: SearchBuildRequest, signal: AbortSignal | undefined = undefined): Promise<any> { + searchBuilds(request: SearchBuildsRequest, signal: AbortSignal | undefined = undefined): Promise<SearchBuildsResponse> { return this.call('buildbucket.v2.Builds', 'SearchBuilds', request, signal); } @@ -222,7 +239,7 @@ return this.call('buildbucket.v2.Builds', 'CancelBuild', request, signal); } - listBuilders(request: ListBuildersRequest, signal: AbortSignal | undefined = undefined): Promise<any> { + listBuilders(request: ListBuildersRequest, signal: AbortSignal | undefined = undefined): Promise<ListBuildersResponse> { return this.call( 'buildbucket.v2.Builders', 'ListBuilders', request, signal); } @@ -232,7 +249,7 @@ * * @returns authorization header to use in requests. */ - private getAuthorizationHeader(): Promise<AuthorizationHeader> { + getAuthorizationHeader(): Promise<AuthorizationHeader> { return getAuthorizationHeader(this.changeId); } }
diff --git a/web/buildbucket-utils.ts b/web/buildbucket-utils.ts index 99cbad7..ca03acb 100644 --- a/web/buildbucket-utils.ts +++ b/web/buildbucket-utils.ts
@@ -87,7 +87,7 @@ * @param loggedIn Whether the user is logged in. * @param change Change object. */ -export function showChooseTryjobs(config: Config, loggedIn: boolean, change: ChangeInfo): boolean { +export function showChooseTryjobs(config: Config | null, loggedIn: boolean, change: ChangeInfo): boolean { return !!(config?.buckets?.length && loggedIn && change.status === 'NEW'); } @@ -100,7 +100,7 @@ * @param loggedIn Whether the user is logged in. * @param change Change object. */ -export function showRetryFailed(config: Config, loggedIn: boolean, change: ChangeInfo): boolean { +export function showRetryFailed(config: Config | null, loggedIn: boolean, change: ChangeInfo): boolean { return !!(config && !config.hideRetryButton && loggedIn && change.status === 'NEW'); } @@ -150,8 +150,8 @@ * and the second with experimental builds. */ export function splitExperimentalBuilds(allBuilds: Build[]): Build[][] { - const builds = []; - const experimentalBuilds = []; + const builds: Build[] = []; + const experimentalBuilds: Build[] = []; allBuilds.forEach(b => { if (isExperimental(b)) { experimentalBuilds.push(b); @@ -182,7 +182,7 @@ export function getFailedBuilders(allBuilds: Build[]): Builder[] { // Only consider non-experimental builds. const [builds] = splitExperimentalBuilds(allBuilds); - const builders = new Map(); + const builders: Map<string, Builder> = new Map(); const seen = new Set(); builds.sort((a, b) => -compareBuildIds(a.id, b.id)); builds.forEach(b => { @@ -254,10 +254,10 @@ 'NO_CODE_CHANGE', ]); const revisions = reversedRevisions(change); - const equivalentSets = []; - let currentSet = new Set(); + const equivalentSets: Set<number>[] = []; + let currentSet: Set<number> = new Set(); for (let i = 0; i < revisions.length; i++) { - currentSet.add(revisions[i]._number); + currentSet.add(revisions[i]._number as number); if (!trivialKinds.has(revisions[i].kind)) { // If this revision was a non-trivial change, it is not part // of the same equivalent patchset set as those after it. @@ -278,11 +278,11 @@ * * @param timestamp A timestamp string with the above format. */ -export function getDateFromTimestamp(timestamp: string): Date { - if (!timestamp) { return null; } +export function getDateFromTimestamp(timestamp: string | undefined): Date | undefined { + if (!timestamp) { return undefined; } const m = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d{1,9})?Z$/ - .exec(timestamp); - if (!m) { return null; } + .exec(String(timestamp)); + if (!m) { return undefined; } // m[2] matches the month with January being 01. The Date constructor takes a // 0-indexed month.
diff --git a/web/checks-fetcher.ts b/web/checks-fetcher.ts index 3552321..ec9dac8 100644 --- a/web/checks-fetcher.ts +++ b/web/checks-fetcher.ts
@@ -13,7 +13,8 @@ RETRY_FAILED_TAG, Builder, BuildRequests, - SearchBuildRequest, + SearchBuildsRequest, + SearchBuildsResponse, } from './buildbucket-client'; import { createBuilderLink, @@ -32,6 +33,7 @@ showRetryFailed, } from './buildbucket-utils'; import { + Artifact, ResultDbV1Client, TestStatus, TestVariant, @@ -45,7 +47,17 @@ } from './resultdb-utils'; import {openTryjobPicker} from './cr-tryjob-picker'; import {PluginApi} from '@gerritcodereview/typescript-api/plugin'; -import {Category, ChangeData, CheckResult, Link, LinkIcon, ResponseCode, Tag, TagColor} from '@gerritcodereview/typescript-api/checks'; +import { + Action, + Category, + ChangeData, + CheckResult, + Link, + LinkIcon, + ResponseCode, + Tag, + TagColor +} from '@gerritcodereview/typescript-api/checks'; import {ChangeInfo} from '@gerritcodereview/typescript-api/rest-api'; // TODO(aravindvasudev): Update to @gerritcodereview/typescript-api/checks implementation once Gerrit updates their API. @@ -86,22 +98,22 @@ const ICON_SPACE = '\u2005\u2005'; export declare interface Build { - id?: string; - builder?: Builder; + id: string; + builder: Builder; number?: number; tags?: { key: string; value: string; }[]; - status?: BuildStatus; + status: BuildStatus; critical?: string; - createTime?: string; - startTime?: string; - endTime?: string; + createTime: string; + startTime: string; + endTime: string; summaryMarkdown?: string; infra?: { resultdb?: { - invocation: boolean; + invocation: string; hostname: string; } }; @@ -131,24 +143,28 @@ } // TODO(aravindvasudev): Update to @gerritcodereview/typescript-api/checks implementation once Gerrit updates their API. -declare interface CheckRun { - patchset: number; - attempt: number; - externalId: string; +export declare interface CheckRun { + change?: number; + patchset?: number; + attempt?: number; + externalId?: string; checkName: string; - checkLink: string; + checkDescription?: string; + checkLink?: string; status: RunStatus; - statusDescription: string; - statusLink: string; - scheduledTimestamp: Date; - startedTimestamp: Date; - finishedTimestamp: Date; - results: any[]; + statusDescription?: string; + statusLink?: string; + labelName?: string; + actions?: Action[]; + scheduledTimestamp?: Date; + startedTimestamp?: Date; + finishedTimestamp?: Date; + results?: CheckResult[]; } export class ChecksFetcher { plugin: PluginApi; - config: Config; + config: Config | null; buildbucketHost: string; maxVariantsPerBuild: number; includeAdditionalResults: boolean; @@ -175,7 +191,7 @@ // first fetch, don't abort. Otherwise, abort using old controller. const controllerKey = this.getChangeDataId(changeData); if (this.controllers.has(controllerKey)) { - this.controllers.get(controllerKey).abort(); + this.controllers.get(controllerKey)?.abort(); } const controller = new AbortController(); this.controllers.set(controllerKey, controller); @@ -185,7 +201,7 @@ if (!this.config) { const pluginName = encodeURIComponent(this.plugin.getPluginName()); const config = await this.plugin.restApi() - .get(`/projects/${encodeURIComponent(repo)}/${pluginName}~config`); + .get(`/projects/${encodeURIComponent(repo)}/${pluginName}~config`) as Config; if (!config) { console.info('Buildbucket plugin not configured for this project.'); return {responseCode: ResponseCode.OK}; @@ -211,7 +227,7 @@ let complete = true; const runs = []; const allAttempts = this.createAttemptList(builds); - const latestPatchset = Object.keys(changeInfo.revisions).length; + const latestPatchset = Object.keys(changeInfo.revisions as {}).length; for (const [i, build] of builds.entries()) { const run = this.convertBuildToRun( @@ -219,7 +235,7 @@ build, patchsetNumber, latestPatchset, - allAttempts[build.builder.builder], + allAttempts[build.builder.builder!], repo); if (!run) { continue; @@ -232,6 +248,11 @@ } const checkResults = await this.convertVariantsToCheckResults( build, (result as PromiseFulfilledResult<TestVariant[]>).value, changeNumber); + + if (!run.results) { + run.results = []; + } + run.results.push(...checkResults); runs.push(run); }; @@ -270,7 +291,7 @@ primary: true, summary: true, tooltip: 'Select specific builders to run on the latest patchset', - callback: (change) => + callback: (change: number) => this.chooseTryjobsCallback(change, latestPatchset, repo), }); } @@ -284,7 +305,7 @@ primary: true, summary: false, tooltip: 'Retry any failed builds from all patchsets', - callback: (change) => + callback: (change: number) => this.retryFailedBuildsCallback( change, latestPatchset, repo, controller), }); @@ -342,7 +363,7 @@ * Compared to fetchBuilds, this method handles fetching equivalent patchsets. */ async fetchDisplayedBuilds(change: number, patchset: number, project: string, changeInfo: ChangeInfo, controller: AbortController): Promise<Build[]> { - let patchsets = []; + let patchsets: number[] = []; const groups = splitPatchsetGroups(changeInfo); for (const group of groups) { @@ -403,7 +424,7 @@ 'infra.resultdb', ].join(','); - let requests: SearchBuildRequest[] = patchsets.map(patchset => ({ + let requests: SearchBuildsRequest[] = patchsets.map(patchset => ({ searchBuilds: { pageSize: 1000, predicate: { @@ -429,18 +450,18 @@ const {responses} = await retry( batch, DEFAULT_UPDATE_INTERVAL_MS, MAX_UPDATE_INTERVAL_MS, 3, 2); - const newRequests = []; - (responses || []).forEach(({searchBuilds, error}, i) => { - if (error) { + const newRequests: SearchBuildsRequest[] = []; + (responses || []).forEach((response: SearchBuildsResponse, i: number) => { + if (response.error) { console.error('Buildbucket request failed with error code ' + - `${error.code}: ${error.message}`); + `${response.error.code}: ${response.error.message}`); return; } - builds.push(...(searchBuilds?.builds || [])); + builds.push(...(response.searchBuilds?.builds || [])); // If there are more builds to fetch, add the request to be re-fetched. - if (searchBuilds?.nextPageToken) { - requests[i].searchBuilds['pageToken'] = searchBuilds.nextPageToken; + if (response.searchBuilds?.nextPageToken) { + requests[i].searchBuilds.pageToken = response.searchBuilds.nextPageToken; newRequests.push(requests[i]); } }); @@ -486,7 +507,7 @@ const buildResourceName = createBuildResourceName(this.buildbucketHost, build.id); const currAttemptNum = attempts.map(a => a.id).indexOf(build.id) + 1; - const run = { + const run: CheckRun = { patchset: currPatchset, attempt: currAttemptNum, externalId: buildResourceName, @@ -530,7 +551,7 @@ // Using the same result category for each previous attempt result means // attempts are grouped together. - run.results.push({ + run.results?.push({ externalId: createBuildResourceName(this.buildbucketHost, currBuild.id), category: maxCategory, summary: this.getResultSummaryFromBuildAttempt(currBuild, attemptNum), @@ -544,7 +565,7 @@ // To minimize clutter for the user, SUCCESSful and STARTED builds should // not contain the Buildbucket CheckResult. Relevant information should be // put in the CheckRun object. - run.results.push({ + run.results?.push({ externalId: buildResourceName, category: maxCategory, summary: this.getResultSummaryFromBuild(build), @@ -572,7 +593,7 @@ }); } - const checkResults = []; + const checkResults: CheckResult[] = []; for (const variant of testVariants) { const variantTags = [...commonTags]; @@ -611,7 +632,7 @@ this.includeAdditionalResults ? variant.results.length : 1; const variantName = createVariantName(variant); // TODO(gavinmak): Simplify data. - const rdbHost = build.infra.resultdb.hostname; + const rdbHost = build.infra?.resultdb?.hostname || ''; const data = { plugin: this.plugin, variant: {variant: variant.variant}, @@ -624,9 +645,9 @@ changeId, }; - const messages = []; - const allInvArtifacts = []; - const resArtifactLinks = []; + const messages: string[] = []; + const allInvArtifacts: Artifact[] = []; + const resArtifactLinks: Link[] = []; const variantLinks = this.createTestResultLinks(variant, build); const client = new ResultDbV1Client(rdbHost, String(changeId)); for (let i = 0; i < numTestResults; i++) { @@ -646,6 +667,7 @@ } // Result artifacts are named 'Run #${runNumber}: ${tooltip}'. + // @ts-ignore resArtifactLinks.sort((a, b) => a.tooltip.localeCompare(b.tooltip)); // Dedupe invocation-level artifacts. @@ -670,12 +692,12 @@ ], message: messages.join(' '), [DATA_SYMBOL]: data, - }); + } as any); } // Sort and group tests by test suite. - function sortResults(element) { - checkResults.sort((a, b) => { + function sortResults(element: string) { + checkResults.sort((a: any, b: any) => { const aName = a[DATA_SYMBOL][element]?.toUpperCase(); const bName = b[DATA_SYMBOL][element]?.toUpperCase(); if (aName < bName) { @@ -915,12 +937,12 @@ * TODO(gavinmak): Refactor to only include build and isQuickRun. */ createAttemptList(builds: Build[]): {[key: string]: Attempt[]} { - const dct = {}; + const dct: {[key: string]: Attempt[]} = {}; builds.forEach(build => { const {id, status} = build; const isQuickRun = !!build.output?.properties?.rts_was_used; const info: Attempt = {id, status, isQuickRun, build}; - const name = build.builder.builder; + const name = build.builder.builder!; if (!(name in dct)) { dct[name] = [info]; } else { @@ -935,8 +957,8 @@ /** * Returns a list of actions used in a Reboot Checks API `Run` object. */ - createRunActions(change: number, build: Build, project: string, patchset: number, attempts: Attempt[]): any[] { - const actions = []; + createRunActions(change: number, build: Build, project: string, patchset: number, attempts: Attempt[]): Action[] { + const actions: Action[] = []; if (!this.retryEnabled) { return actions; } @@ -998,7 +1020,7 @@ const {responses} = await retry( batch, DEFAULT_UPDATE_INTERVAL_MS, MAX_UPDATE_INTERVAL_MS, 3, 2); let numError = 0; - (responses || []).forEach(r => { + (responses || []).forEach((r: {error: any}) => { if (r.error) { numError++; console.warn('Batch request failed:', r.error); @@ -1017,7 +1039,7 @@ * ActionCallback that cancels any existing builds. If a build is in an end * state, this is a no-op. */ - async cancelRunsCallback(change: number, builds: Build[]): Promise<{message: string}> { + async cancelRunsCallback(change: number, builds: Attempt[]): Promise<{message: string}> { const requests = builds.map(build => ({ cancelBuild: { id: build.id, @@ -1098,7 +1120,7 @@ /** * Cleans up a string to be used for sorting results. */ - sortName(s) { + sortName(s: string) { return (s || '').replace(/[^\x00-\x7F]+/gm, '').trim().toUpperCase(); } @@ -1116,7 +1138,7 @@ * * @returns authorization header to use in requests. */ - private getAuthorizationHeader(changeId: number): Promise<AuthorizationHeader> { + getAuthorizationHeader(changeId: number): Promise<AuthorizationHeader> { return getAuthorizationHeader(String(changeId)); } } @@ -1129,7 +1151,7 @@ * @param number The numbered run this belongs to. If this is null or * undefined, the link is not labeled with a run number. */ -export function createArtifactLink(artifact: any, isInvLevel: boolean, number: number): Link { +export function createArtifactLink(artifact: any, isInvLevel: boolean, number: number | null): Link { let tooltip = number != null ? `Run ${number}: ` : ''; tooltip += artifact.artifactId + (isInvLevel ? ' of Parent Invocation' : ''); return { @@ -1140,7 +1162,7 @@ }; } -export function createTestResultSummary(result: any): string { +export function createTestResultSummary(result: any): string | null { const {status, expected} = result; const prefix = expected ? 'expectedly ' : 'unexpectedly '; if (status === TestStatus.PASS) {
diff --git a/web/checks-result.ts b/web/checks-result.ts index 7a8f3ec..84d6241 100644 --- a/web/checks-result.ts +++ b/web/checks-result.ts
@@ -10,6 +10,7 @@ createArtifactLink, createTestResultSummary, } from './checks-fetcher'; +import {Link} from '@gerritcodereview/typescript-api/checks'; // The maximum number of bytes to fetch when fetching text artifacts. const MAX_ARTIFACT_SIZE = 50000; // 50 KB @@ -92,7 +93,7 @@ * * @param element: the check-result-expanded element. */ -export async function installChecksResult(element) { +export async function installChecksResult(element: any) { if (!element?.result?.message) return; // Remove message div with raw message string. @@ -117,9 +118,9 @@ EXPANDED_TEST_ROW_STYLE); const {run: checkRun, result: checkResult} = element; - const allInvArtifacts = []; - const resArtifactLinks = []; - const htmlPromises = data.testResults.map(async (r, i) => { + const allInvArtifacts: Artifact[] = []; + const resArtifactLinks: Link[] = []; + const htmlPromises = data.testResults.map(async (r: any, i: number) => { const testResult = r.result; const {summaryHtml, name} = testResult; // Fetch artifacts. @@ -156,6 +157,7 @@ // Update CheckResult links if not already added. if (!data.addedArtifactLinks) { // Result artifacts are named 'Run #${runNumber}: ${tooltip}'. + // @ts-ignore resArtifactLinks.sort((a, b) => a.tooltip.localeCompare(b.tooltip)); // Dedupe invocation-level artifacts. @@ -185,7 +187,7 @@ let exonerations = ''; if (data.testExonerations?.length > 0) { exonerations = '<br /><br />' + - data.testExonerations.map(e => e.explanationHtml).join('<br />'); + data.testExonerations.map((e: any) => e.explanationHtml).join('<br />'); } // Use <ul> instead of <ol> since results are labeled 'Run #x'. @@ -224,8 +226,8 @@ await Promise.allSettled([...textArtifacts].map(async element => { const artifactMap = element.hasAttribute('inv-level') ? invArtifactMap : resArtifactMap; - const id = element.getAttribute('artifact-id'); - const {fetchUrl, name, sizeBytes} = artifactMap.get(id); + const id = element.getAttribute('artifact-id') as string; + const {fetchUrl, name, sizeBytes} = artifactMap.get(id)!; if (!fetchUrl) { return; } if (!sizeBytes) { element.replaceWith(metadataElement(`${id} artifact is empty`)); @@ -268,7 +270,7 @@ // HTML. const nodes = await nodePromise; // Replacing without copying the nodes may lead to empty artifacts. - const copy = nodes.map(n => n.cloneNode(true)); + const copy = nodes.map((n: Node) => n.cloneNode(true)); element.replaceWith(...copy); } catch (e: unknown) { // Evict the rejected promise from the cache. @@ -323,19 +325,19 @@ * @param md: the markdown to parse. */ export function parseMarkdown(md: string) { - function process(md) { + function process(md: string) { // Create links. md = md.replaceAll( /\[((?!\]\()[\s\S]*?)?\]\(([^\)]*)\)/gm, - (_m, p1, p2) => `<a href='${p2}'>${p1 || ''}</a>`); + (_m: any, p1: string, p2: string) => `<a href='${p2}'>${p1 || ''}</a>`); // Replace ** and __ strings. md = md.replaceAll( /(\*\*|__)(([\S]((?!\1)[ \t\S])*?)?[\S])\1/gm, - (_m, _p1, p2) => `<strong>${p2}</strong>`); + (_m: any, _p1: string, p2: string) => `<strong>${p2}</strong>`); // Replace # with <h1> and so on up to <h6>. - md = md.replaceAll(/^(#+) (.*)/gm, (m, p1, p2) => { + md = md.replaceAll(/^(#+) (.*)/gm, (m: any, p1: string, p2: string) => { if (p1.length > 6) { return m; }
diff --git a/web/cr-tryjob-picker.ts b/web/cr-tryjob-picker.ts index 2c3ac84..c067616 100644 --- a/web/cr-tryjob-picker.ts +++ b/web/cr-tryjob-picker.ts
@@ -5,11 +5,17 @@ import {PluginApi} from '@gerritcodereview/typescript-api/plugin'; import {css, html, LitElement} from 'lit'; import {customElement, property, state} from 'lit/decorators'; -import {BuildbucketV2Client, Builder, makeBuildRequests} from './buildbucket-client'; +import {BuildbucketV2Client, Builder, GerritChange, makeBuildRequests} from './buildbucket-client'; import {getNewOperationId} from './buildbucket-utils'; import {Config} from './checks-fetcher'; -let _TRYJOB_PICKER = null; +let _TRYJOB_PICKER: any = null; + +declare interface Bucket { + project: string; + bucket: string; + builders: string[]; +} /** * Open a CrTryjobPicker popup with the given configuration and change info. @@ -21,7 +27,7 @@ * @param change: the change number * @param patchset: the patchset number */ -export async function openTryjobPicker(plugin: PluginApi, pluginConfig: Config, buildbucketHost: string, +export async function openTryjobPicker(plugin: PluginApi, pluginConfig: Config | null, buildbucketHost: string, project: string, change: number, patchset: number) { if (_TRYJOB_PICKER) { _TRYJOB_PICKER.close(); @@ -47,16 +53,16 @@ @customElement('cr-tryjob-picker') export class CrTryjobPicker extends LitElement { @property() - buildbucketHost: string; + buildbucketHost: string | undefined; @property() change: {_number?: number, project?: string} = {}; @property() - revision: {_number?: number}; + revision: { _number?: number; } | undefined; @property() - plugin: PluginApi; + plugin: PluginApi | undefined; @property() disabled: boolean = false; @@ -65,16 +71,16 @@ bucketListMessage: string = ''; @state() - buckets: any[] = []; + buckets: Bucket[] = []; @state() - clientOperationId: string; + clientOperationId: string | undefined; @state() - filterClass: string; + filterClass: string | undefined; @state() - matchesFilter: ((string) => boolean) = _ => true; + matchesFilter: ((arg0: string) => boolean) = _ => true; @state() selectedBuilders: {[key: string]: Builder} = {}; @@ -207,7 +213,7 @@ <input autofocus class="filter ${this.filterClass}" placeholder="Regex filter" - @input=${e => this.filter = e.target.value}> + @input=${(e: Event) => this.filter = (e.target as HTMLInputElement)?.value}> <div class="list"> <div>${this.bucketListMessage}</div> ${bucketsHTML} @@ -256,7 +262,7 @@ if (!response.builders || response.builders.length === 0) { break; } - builders.push(...response.builders.map(b => b.id.builder)); + builders.push(...response.builders.map(b => b.id?.builder ?? '')); pageToken = response.nextPageToken; if (!pageToken) { break; @@ -285,8 +291,8 @@ const client = new BuildbucketV2Client( this.buildbucketHost, String(this.change._number)); - const buckets = new Map(); - const promises = []; + const buckets: Map<string, {project: string; bucket: string; builders: string[]}> = new Map(); + const promises: Promise<any>[] = []; cfgBuckets.forEach(b => { const luciPrefixLength = 'luci.'.length; @@ -306,7 +312,9 @@ } const {project, bucket, builders} = result.value; const b = buckets.get(`${project}/${bucket}`); - b.builders = Array.from(new Set(b.builders.concat(builders))).sort(); + if (b) { + b.builders = Array.from(new Set(b.builders.concat(builders))).sort(); + } }); return Array.from(buckets.values()); } @@ -363,8 +371,8 @@ } } - computeBucketFilterFn(builderPredicate: (string) => boolean) { - return bucket => bucket.builders.some(builderPredicate); + computeBucketFilterFn(builderPredicate: (arg0: string) => boolean) { + return (bucket: Bucket) => bucket.builders.some(builderPredicate); } private computeAddButtonDisabled(selectedBuilders: {[key: string]: Builder}, disabled: boolean) { @@ -397,7 +405,7 @@ if (!bucketToBuilders[bucket]) { bucketToBuilders[bucket] = []; } - bucketToBuilders[bucket].push(b.builder); + bucketToBuilders[bucket].push(b.builder!); }); const includeTrybotStrings = []; for (let [bucket, builders] of Object.entries(bucketToBuilders)) { @@ -416,7 +424,7 @@ const project = el.getAttribute('data-project'); const bucket = el.getAttribute('data-bucket'); const builder = el.value; - const builderKey = this.builderKey(project, bucket, builder); + const builderKey = this.builderKey(project!, bucket!, builder); if (el.checked) { this.selectedBuilders = Object.assign( this.selectedBuilders, {[builderKey]: {project, bucket, builder}}); @@ -434,11 +442,11 @@ private async scheduleSelectedBuilders(): Promise<void> { this.clientOperationId = this.clientOperationId || getNewOperationId(); const builders = Object.values(this.selectedBuilders); - const gerritChanges = [{ + const gerritChanges: GerritChange[] = [{ host: this.pluginConfig.gerritHost, - change: this.change._number, - project: this.change.project, - patchset: this.revision._number, + change: this.change._number!, + project: this.change.project!, + patchset: this.revision!._number!, }]; const requests = makeBuildRequests( builders, @@ -446,9 +454,9 @@ this.clientOperationId); const client = new BuildbucketV2Client( - this.buildbucketHost, String(this.change._number)); + this.buildbucketHost!, String(this.change._number)); const {responses} = await client.batch(requests); - (responses || []).forEach(r => { + (responses || []).forEach((r: any) => { if (r.error) { throw new Error(r.error.message); } @@ -498,7 +506,7 @@ /** Resets the state of the picker element. */ private reset(): void { - this.clientOperationId = null; + this.clientOperationId = undefined; this.filter = ''; this.selectedBuilders = {}; this.uncheckAllBoxes(); @@ -511,8 +519,7 @@ // change event, which means that binding the value to selectedBuilders // won't actually cause the checkbox to be unchecked. This must be done // manually. - const checkboxes = this.shadowRoot - .querySelectorAll('input[type="checkbox"]') as unknown as Element[]; + const checkboxes = this.shadowRoot?.querySelectorAll('input[type="checkbox"]') as unknown as Element[]; for (const c of checkboxes) { (c as HTMLInputElement).checked = false; }
diff --git a/web/plugin.ts b/web/plugin.ts index d601a5c..559693e 100644 --- a/web/plugin.ts +++ b/web/plugin.ts
@@ -22,8 +22,9 @@ const buildbucketHost = 'cr-buildbucket.appspot.com'; const fetcher = new ChecksFetcher(plugin, buildbucketHost, 100); + // TODO(aravindvasudev): Update to @gerritcodereview/typescript-api/checks implementation once Gerrit updates their API. plugin.checks().register( - {fetch: (changeData) => fetcher.fetch(changeData)}, + {fetch: (changeData) => (fetcher.fetch(changeData) as any)}, {fetchPollingIntervalSeconds: 10}, ); plugin.hook('check-result-expanded').onAttached(installChecksResult);
diff --git a/web/resultdb-client.ts b/web/resultdb-client.ts index 8ccb012..7ae5e20 100644 --- a/web/resultdb-client.ts +++ b/web/resultdb-client.ts
@@ -44,13 +44,16 @@ testId?: string; resultId?: string; tags: {[key: string]: string}[]; + status?: TestStatus; + summaryHtml?: string; + expected?: boolean; } export declare interface Artifact { name: string; artifactId: string; fetchUrl: string; - fetchUrlExpiration?: Date; + fetchUrlExpiration?: string; contentType?: string; sizeBytes: number; contents?: any; @@ -97,7 +100,7 @@ * @param signal An AbortSignal object. * @return Response body. */ - private async call(service: string, method: string, request: TestVariantsQuery | listAllArtifactsQuery, signal: AbortSignal = undefined): Promise<any> { + private async call(service: string, method: string, request: TestVariantsQuery | listAllArtifactsQuery, signal: AbortSignal | undefined = undefined): Promise<any> { // Miniature implementation of pRPC protocol with JSONPB and auth. const authHeader = await this.getAuthorizationHeader(); const headers = { @@ -141,7 +144,7 @@ 'luci.resultdb.v1.ResultDB', 'QueryTestVariants', request, signal); } - listArtifacts(request: listAllArtifactsQuery): Promise<{artifacts: Artifact[], nextPageToken: string}> { + listArtifacts(request: listAllArtifactsQuery): Promise<{artifacts: Artifact[], nextPageToken?: string}> { return this.call('luci.resultdb.v1.ResultDB', 'ListArtifacts', request); } @@ -162,7 +165,7 @@ } const response = await this.queryTestVariants({ - invocations: [build.infra.resultdb.invocation], + invocations: [build.infra?.resultdb?.invocation], predicate: { status: inclAdditional ? TestVariantStatus.UNEXPECTED_MASK @@ -289,7 +292,7 @@ * * @returns authorization header to use in requests. */ - private getAuthorizationHeader(): Promise<AuthorizationHeader> { + getAuthorizationHeader(): Promise<AuthorizationHeader> { return getAuthorizationHeader(this.changeId); } }
diff --git a/web/resultdb-utils.ts b/web/resultdb-utils.ts index fc11e7a..202bd7b 100644 --- a/web/resultdb-utils.ts +++ b/web/resultdb-utils.ts
@@ -4,14 +4,14 @@ import {getDateFromTimestamp} from './buildbucket-utils'; import {Build} from './checks-fetcher'; -import {TestResult, TestVariant} from './resultdb-client'; +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 = { +const URL_ESCAPES: {[key: string]: string} = { '(': '%28', ')': '%29', }; @@ -25,25 +25,25 @@ * @param build Buildbucket build. */ export function createLayoutResultLink(variant: TestVariant, build: Build): string { - if (!/^ninja:\/\/:blink_web_tests\//.test(variant.testId) || + 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; + .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 = /^([^\)]*\)?)/.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}/` + + `${encodeURIComponent(build.builder.builder!)}/${build.number}/` + `${stepName}/layout-test-results/results.html`; } @@ -57,10 +57,10 @@ const builder = build.builder; return encodeURI(`https://ci.chromium.org/ui/p/${builder.project}/` + `builders/${builder.bucket}/` + - `${encodeURIComponent(builder.builder)}/`) + + `${encodeURIComponent(builder.builder!)}/`) + `b${build.id}/test-results?q=ExactID%3A` + - `${encodeURIComponent(variant.testId)}+VHash%3A` + - `${encodeURIComponent(variant.variantHash)}&clean=`; + `${encodeURIComponent(variant.testId!)}+VHash%3A` + + `${encodeURIComponent(variant.variantHash!)}&clean=`; } /** @@ -76,7 +76,7 @@ } // 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; + const taskId = testResult.tags?.find(t => t['key'] === 'swarming_task_id')?.['value']; if (taskId) { return `https://chromium-swarm.appspot.com/task?id=${taskId}`; } @@ -99,7 +99,7 @@ */ export function getParentInv(resultName: string): string { const match = /^(invocations\/.+?)\//.exec(resultName); - return match && match[1]; + return (match && match[1]) ?? ''; } /** @@ -107,7 +107,7 @@ * returned fetchUrlExpiration is represented as the number of milliseconds * since the Unix Epoch. */ -export function getEarliestArtifactExpiration(artifacts): number { +export function getEarliestArtifactExpiration(artifacts: Artifact[]): number { return Math.min(...artifacts - .map(a => getDateFromTimestamp(a.fetchUrlExpiration).getTime())); + .map(a => getDateFromTimestamp(a.fetchUrlExpiration)!.getTime())); }
diff --git a/web/sanitizer.ts b/web/sanitizer.ts index 5f1cb9f..af56546 100644 --- a/web/sanitizer.ts +++ b/web/sanitizer.ts
@@ -10,7 +10,7 @@ */ // Characters in an HTML string to escape. -const ESCAPES = { +const ESCAPES: {[key: string]: string} = { '&': '&', '<': '<', '>': '>', @@ -55,7 +55,7 @@ switch (n.nodeType) { case 3: // TEXT_NODE // Print it escaped. - this.safeString.push(this.escape(n.textContent)); + this.safeString.push(this.escape(n.textContent!)); break; case 1: // ELEMENT_NODE @@ -144,7 +144,7 @@ * @returns The escaped HTML string. */ escape(s: string): string { - return s.replaceAll(ESCAPE_REGEX, m => ESCAPES[m]); + return s.replaceAll(ESCAPE_REGEX, (m: string) => ESCAPES[m]); } }
diff --git a/web/test/auth_test.ts b/web/test/auth_test.ts index 4b2a689..d34a7a2 100644 --- a/web/test/auth_test.ts +++ b/web/test/auth_test.ts
@@ -1,18 +1,19 @@ import {assertRejects} from './test-util'; import './test-setup'; -import {Authenticator, initAuth, getAuthorizationHeader, resetAuthState, JWT_TOKEN_ID, JWT_TIMEOUT_MILLISECONDS, GERRIT_JWT_FLAG} from '../auth'; +import {Authenticator, initAuth, getAuthorizationHeader, resetAuthState, JWT_TOKEN_ID, JWT_TIMEOUT_MILLISECONDS, GERRIT_JWT_FLAG, Token, OAuthConfig} from '../auth'; +import {PluginApi} from '@gerritcodereview/typescript-api/plugin'; const platformJsElement = document.getElementById('platformJs') as HTMLScriptElement; suite('auth', () => { - let sandbox; - let authenticator; - let plugin; - let accessToken; - let jwtToken; - let jwtResponse; - let clock; - let changeId; + let sandbox: sinon.SinonSandbox; + let authenticator: Authenticator; + let plugin: PluginApi; + let accessToken: Token; + let jwtToken: Token; + let jwtResponse: {jwts: any}; + let clock: sinon.SinonFakeTimers; + let changeId: string; setup(() => { sandbox = sinon.createSandbox({}); @@ -31,7 +32,7 @@ window.gapi = { auth2: { - authorize: (_, resolve) => resolve(accessToken), + authorize: (_: any, resolve: any) => resolve(accessToken), }, }; @@ -43,12 +44,12 @@ } }; - changeId = 123; + changeId = '123'; plugin = { restApi: () => ({ get: () => jwtResponse }) - } + } as unknown as PluginApi; // Test with Mendel flag enabled if (!window.ENABLED_EXPERIMENTS) { @@ -99,7 +100,7 @@ test('getToken rejects when authorize times out', async () => { jwtResponse.jwts = {}; const forever = new Promise(() => {}); - sandbox.stub(gapi.auth2, 'authorize').callsFake(forever); + sandbox.stub(gapi.auth2, 'authorize').callsFake(forever as any); assertRejects(authenticator.getToken(changeId), 'timeout'); }); @@ -107,7 +108,7 @@ sandbox.spy(plugin, 'restApi'); const actualToken1 = await authenticator.getToken(changeId); const actualToken2 = await authenticator.getToken(changeId); - assert.isTrue(plugin.restApi.calledOnce); + assert.isTrue((plugin.restApi as any).calledOnce); assert.deepEqual(actualToken1, jwtToken); assert.deepEqual(actualToken2, jwtToken); }); @@ -120,15 +121,15 @@ ]); assert.deepEqual(actualToken1, jwtToken); assert.deepEqual(actualToken2, jwtToken); - assert.isTrue(plugin.restApi.calledOnce); + assert.isTrue((plugin.restApi as any).calledOnce); }); }); suite('initAuth', () => { - let sandbox; - let plugin; - let loggedIn; - let cfg; + let sandbox: sinon.SinonSandbox; + let plugin: PluginApi; + let loggedIn: boolean; + let cfg: OAuthConfig; const accessToken = 'token'; setup(() => { @@ -143,21 +144,22 @@ getLoggedIn: async () => loggedIn, get: async (_) => cfg, }), - }; + } as PluginApi; window.gapi = { auth2: { - init: (resolve) => resolve(), + init: (resolve: any) => resolve(), }, config: { get: () => null, update: () => {}, }, - load: (_, resolve) => resolve(), + load: (_: any, resolve: any) => resolve(), }; - sandbox.stub(Authenticator.prototype, 'getToken').callsFake(() => ({ + sandbox.stub(Authenticator.prototype, 'getToken').callsFake(async () => ({ access_token: accessToken, + expires_at: 0, })); });
diff --git a/web/test/buildbucket-client_test.ts b/web/test/buildbucket-client_test.ts index a29d2bf..bef0625 100644 --- a/web/test/buildbucket-client_test.ts +++ b/web/test/buildbucket-client_test.ts
@@ -1,11 +1,11 @@ import '../cache-object'; import { - BuildbucketV2Client, + BuildbucketV2Client, BuildRequests, } from '../buildbucket-client'; suite('BuildbucketV2Client tests', () => { - let sandbox; - let client; + let sandbox: sinon.SinonSandbox; + let client: BuildbucketV2Client; setup(() => { sandbox = sinon.createSandbox(); @@ -24,16 +24,23 @@ sandbox.stub(window, 'fetch').returns(Promise.resolve({ ok: true, text: async () => `)]}'${JSON.stringify(expected)}`, - })); + } as Response)); - const req = { - searchBuilds: { - predicate: { - builder: { - project: 'chromium', + const req: BuildRequests = { + requests: [ + { + searchBuilds: { + pageSize: 100, + predicate: { + includeExperimental: false, + gerritChanges: [], + builder: { + project: 'chromium', + }, + }, }, - }, - }, + } + ] }; const actual = await client.batch(req); assert.deepEqual(actual, expected);
diff --git a/web/test/buildbucket-utils_test.ts b/web/test/buildbucket-utils_test.ts index eff8cc2..a0f399b 100644 --- a/web/test/buildbucket-utils_test.ts +++ b/web/test/buildbucket-utils_test.ts
@@ -19,6 +19,7 @@ showRetryFailed, } from '../buildbucket-utils'; import {ChangeInfo, ChangeStatus, NumericChangeId, RepoName, RevisionInfo, RevisionKind} from '@gerritcodereview/typescript-api/rest-api'; +import { Build } from '../checks-fetcher.js'; suite('buildbucket utils', () => { test('compareBuildIds sorts build ID strings in decreasing order', () => { @@ -31,24 +32,23 @@ }); test('isExperimental returns true for experimental builds', () => { - assert.isFalse(isExperimental({})); assert.isTrue(isExperimental( - {input: {experiments: ['luci.non_production']}} + {input: {experiments: ['luci.non_production']}} as Build )); assert.isFalse(isExperimental( - {input: {experiments: ['luci.use_realms']}} + {input: {experiments: ['luci.use_realms']}} as Build )); assert.isFalse(isExperimental( - {input: {experiments: []}} + { input: { experiments: [] } } as unknown as Build )); assert.isTrue(isExperimental( - {tags: [{key: 'cq_experimental', value: 'true'}]} + {tags: [{key: 'cq_experimental', value: 'true'}]} as Build )); assert.isFalse(isExperimental( - {tags: [{key: 'cq_experimental', value: 'false'}]} + {tags: [{key: 'cq_experimental', value: 'false'}]} as Build )); assert.isFalse(isExperimental( - {tags: [{key: 'foo', value: 'true'}]} + {tags: [{key: 'foo', value: 'true'}]} as Build )); }); @@ -67,201 +67,111 @@ test('showChooseTryjobs depends on config, change, and user status', () => { assert.isTrue(showChooseTryjobs({buckets: [{name: 'foo'}]}, true, { status: ChangeStatus.NEW, - id: undefined, - project: undefined, - branch: undefined, - change_id: undefined, - subject: '', - created: undefined, - updated: undefined, - insertions: 0, - deletions: 0, - _number: undefined, - owner: undefined, - reviewers: undefined - })); + id: '', + project: '', + branch: '', + } as ChangeInfo)); assert.isFalse(showChooseTryjobs(null, true, { status: ChangeStatus.NEW, - id: undefined, - project: undefined, - branch: undefined, - change_id: undefined, - subject: '', - created: undefined, - updated: undefined, - insertions: 0, - deletions: 0, - _number: undefined, - owner: undefined, - reviewers: undefined - })); + id: '', + project: '', + branch: '', + } as ChangeInfo)); assert.isFalse(showChooseTryjobs({buckets: []}, true, { status: ChangeStatus.NEW, - id: undefined, - project: undefined, - branch: undefined, - change_id: undefined, - subject: '', - created: undefined, - updated: undefined, - insertions: 0, - deletions: 0, - _number: undefined, - owner: undefined, - reviewers: undefined - })); + id: '', + project: '', + branch: '', + } as ChangeInfo)); assert.isFalse(showChooseTryjobs({buckets: [{name: 'foo'}]}, false, { status: ChangeStatus.NEW, - id: undefined, - project: undefined, - branch: undefined, - change_id: undefined, - subject: '', - created: undefined, - updated: undefined, - insertions: 0, - deletions: 0, - _number: undefined, - owner: undefined, - reviewers: undefined - })); + id: '', + project: '', + branch: '', + } as ChangeInfo)); assert.isFalse(showChooseTryjobs({buckets: [{name: 'foo'}]}, true, { status: ChangeStatus.MERGED, - id: undefined, - project: undefined, - branch: undefined, - change_id: undefined, - subject: '', - created: undefined, - updated: undefined, - insertions: 0, - deletions: 0, - _number: undefined, - owner: undefined, - reviewers: undefined - })); + id: '', + project: '', + branch: '', + } as ChangeInfo)); }); test('showRetryFailed depends on config, change, and user status', () => { assert.isTrue(showRetryFailed({hideRetryButton: false}, true, { status: ChangeStatus.NEW, - id: undefined, - project: undefined, - branch: undefined, - change_id: undefined, - subject: '', - created: undefined, - updated: undefined, - insertions: 0, - deletions: 0, - _number: undefined, - owner: undefined, - reviewers: undefined - })); + id: '', + project: '', + branch: '', + } as ChangeInfo)); assert.isFalse(showRetryFailed({hideRetryButton: true}, true, { status: ChangeStatus.NEW, - id: undefined, - project: undefined, - branch: undefined, - change_id: undefined, - subject: '', - created: undefined, - updated: undefined, - insertions: 0, - deletions: 0, - _number: undefined, - owner: undefined, - reviewers: undefined - })); + id: '', + project: '', + branch: '', + } as ChangeInfo)); assert.isFalse(showRetryFailed(null, true, { status: ChangeStatus.NEW, - id: undefined, - project: undefined, - branch: undefined, - change_id: undefined, - subject: '', - created: undefined, - updated: undefined, - insertions: 0, - deletions: 0, - _number: undefined, - owner: undefined, - reviewers: undefined - })); + id: '', + project: '', + branch: '', + } as ChangeInfo)); assert.isFalse(showRetryFailed({hideRetryButton: false}, false, { status: ChangeStatus.NEW, - id: undefined, - project: undefined, - branch: undefined, - change_id: undefined, - subject: '', - created: undefined, - updated: undefined, - insertions: 0, - deletions: 0, - _number: undefined, - owner: undefined, - reviewers: undefined - })); + id: '', + project: '', + branch: '', + } as ChangeInfo)); assert.isFalse(showRetryFailed({hideRetryButton: false}, true, { status: ChangeStatus.MERGED, - id: undefined, - project: undefined, - branch: undefined, - change_id: undefined, - subject: '', - created: undefined, - updated: undefined, - insertions: 0, - deletions: 0, - _number: undefined, - owner: undefined, - reviewers: undefined - })); + id: '', + project: '', + branch: '', + } as ChangeInfo)); }); test('shouldSkipRetry returns true for skipped builds', () => { assert.isTrue(shouldSkipRetry( - {tags: [{key: 'skip-retry-in-gerrit', value: 'true'}]} + {tags: [{key: 'skip-retry-in-gerrit', value: 'true'}]} as Build )); assert.isFalse(shouldSkipRetry( - {tags: [{key: 'skip-retry-in-gerrit', value: 'false'}]} + {tags: [{key: 'skip-retry-in-gerrit', value: 'false'}]} as Build )); assert.isFalse(shouldSkipRetry( - {tags: [{key: 'foo', value: 'true'}]} + {tags: [{key: 'foo', value: 'true'}]} as Build )); }); test('hasHideInGerritTag returns true for hidden builds', () => { assert.isTrue(hasHideInGerritTag( - {tags: [{key: 'hide-in-gerrit', value: 'true'}]} + {tags: [{key: 'hide-in-gerrit', value: 'true'}]} as Build )); assert.isTrue(hasHideInGerritTag( - {tags: [{key: 'hide-in-gerrit', value: 'pointless'}]} + {tags: [{key: 'hide-in-gerrit', value: 'pointless'}]} as Build )); assert.isFalse(hasHideInGerritTag( - {tags: [{key: 'hide-in-gerrit', value: 'false'}]} + {tags: [{key: 'hide-in-gerrit', value: 'false'}]} as Build )); assert.isFalse(hasHideInGerritTag( - {tags: [{key: 'foo', value: 'true'}]} + {tags: [{key: 'foo', value: 'true'}]} as Build )); }); test('hasHideTestResultsInGerritTag returns true for tagged builds', () => { assert.isFalse(hasHideTestResultsInGerritTag( - {tags: []} + { tags: [] } as unknown as Build )); assert.isTrue(hasHideTestResultsInGerritTag( - {tags: [{key: 'hide-test-results-in-gerrit', value: 'true'}]} + {tags: [{key: 'hide-test-results-in-gerrit', value: 'true'}]} as Build )); assert.isTrue(hasHideTestResultsInGerritTag( - {tags: [{key: 'hide-test-results-in-gerrit', value: 'pointless'}]} + {tags: [{key: 'hide-test-results-in-gerrit', value: 'pointless'}]} as Build )); assert.isFalse(hasHideTestResultsInGerritTag( - {tags: [{key: 'hide-test-results-in-gerrit', value: 'false'}]} + {tags: [{key: 'hide-test-results-in-gerrit', value: 'false'}]} as Build )); assert.isFalse(hasHideTestResultsInGerritTag( - {tags: [{key: 'foo', value: 'true'}]} + {tags: [{key: 'foo', value: 'true'}]} as Build )); }); @@ -302,13 +212,13 @@ }); test('getFailedBuilders with one failed build', () => { - const builds = [ + const builds: Build[] = [ { id: '1', status: BuildStatus.FAILURE, builder: {project: 'chromium', bucket: 'try', builder: 'linux-rel'}, tags: [{key: 'cq_experimental', value: 'false'}], - }, + } as Build, ]; assert.deepEqual(getFailedBuilders(builds), [ {project: 'chromium', bucket: 'try', builder: 'linux-rel'}, @@ -316,43 +226,43 @@ }); test('getFailedBuilders with an experimental build', () => { - const builds = [ + const builds: Build[] = [ { id: '1', status: BuildStatus.FAILURE, builder: {project: 'chromium', bucket: 'try', builder: 'linux-rel'}, tags: [{key: 'cq_experimental', value: 'true'}], - }, + } as Build, ]; assert.deepEqual(getFailedBuilders(builds), []); }); test('getFailedBuilders with a successful build', () => { - const builds = [ + const builds: Build[] = [ { id: '1', status: BuildStatus.SUCCESS, builder: {project: 'chromium', bucket: 'try', builder: 'linux-rel'}, tags: [{key: 'cq_experimental', value: 'false'}], - }, + } as Build, ]; assert.deepEqual(getFailedBuilders(builds), []); }); test('getFailedBuilders with a failed then successful build', () => { - const builds = [ + const builds: Build[] = [ { id: '1', status: BuildStatus.SUCCESS, builder: {project: 'chromium', bucket: 'try', builder: 'linux-rel'}, tags: [{key: 'cq_experimental', value: 'false'}], - }, + } as Build, { id: '2', status: BuildStatus.FAILURE, builder: {project: 'chromium', bucket: 'try', builder: 'linux-rel'}, tags: [{key: 'cq_experimental', value: 'false'}], - }, + } as Build, ]; assert.deepEqual(getFailedBuilders(builds), []); }); @@ -361,37 +271,37 @@ // Below, the running build is not the latest build. // This is unusual, but this test should verify that // even in this case, no builds should be retried. - const builds = [ + const builds: Build[] = [ { id: '1', status: BuildStatus.FAILURE, builder: {project: 'chromium', bucket: 'try', builder: 'linux-rel'}, tags: [{key: 'cq_experimental', value: 'false'}], - }, + } as Build, { id: '2', status: BuildStatus.STARTED, builder: {project: 'chromium', bucket: 'try', builder: 'linux-rel'}, tags: [{key: 'cq_experimental', value: 'false'}], - }, + } as Build, ]; assert.deepEqual(getFailedBuilders(builds), []); }); test('getFailedBuilders with multiple failed builds, one builder', () => { - const builds = [ + const builds: Build[] = [ { id: '1', status: BuildStatus.FAILURE, builder: {project: 'chromium', bucket: 'try', builder: 'linux-rel'}, tags: [{key: 'cq_experimental', value: 'false'}], - }, + } as Build, { id: '2', status: BuildStatus.FAILURE, builder: {project: 'chromium', bucket: 'try', builder: 'linux-rel'}, tags: [{key: 'cq_experimental', value: 'false'}], - }, + } as Build, ]; assert.deepEqual(getFailedBuilders(builds), [ {project: 'chromium', bucket: 'try', builder: 'linux-rel'}, @@ -399,19 +309,19 @@ }); test('getFailedBuilders with multiple failed builds', () => { - const builds = [ + const builds: Build[] = [ { id: '1', status: BuildStatus.FAILURE, builder: {project: 'chromium', bucket: 'try', builder: 'linux-rel'}, tags: [{key: 'cq_experimental', value: 'false'}], - }, + } as Build, { id: '2', status: BuildStatus.FAILURE, builder: {project: 'chromium', bucket: 'try', builder: 'mac-rel'}, tags: [{key: 'cq_experimental', value: 'false'}], - }, + } as Build, ]; assert.deepEqual(getFailedBuilders(builds), [ {project: 'chromium', bucket: 'try', builder: 'linux-rel'}, @@ -420,19 +330,19 @@ }); test('getFailedBuilders with skippable failed builds', () => { - const builds = [ + const builds: Build[] = [ { id: '1', status: BuildStatus.FAILURE, builder: {project: 'chromium', bucket: 'try', builder: 'linux-rel'}, tags: [{key: 'skip-retry-in-gerrit', value: 'false'}], - }, + } as Build, { id: '2', status: BuildStatus.FAILURE, builder: {project: 'chromium', bucket: 'try', builder: 'mac-rel'}, tags: [{key: 'skip-retry-in-gerrit', value: 'true'}], - }, + } as Build, ]; assert.deepEqual(getFailedBuilders(builds), [ {project: 'chromium', bucket: 'try', builder: 'linux-rel'}, @@ -460,7 +370,7 @@ deletions: 0, owner: undefined, reviewers: undefined - }; + } as unknown as ChangeInfo; assert.deepEqual( reversedRevisions(change), [ @@ -489,7 +399,7 @@ deletions: 0, owner: undefined, reviewers: undefined - }; + } as unknown as ChangeInfo; assert.isTrue(isLatestPatchset(change, 2)); assert.isFalse(isLatestPatchset(change, 1)); }); @@ -518,7 +428,7 @@ deletions: 0, owner: undefined, reviewers: undefined - }; + } as unknown as ChangeInfo; assert.deepEqual(splitPatchsetGroups(change), [ new Set([6]), new Set([4, 5]), @@ -542,23 +452,23 @@ deletions: 0, owner: undefined, reviewers: undefined - }; + } as unknown as ChangeInfo; assert.deepEqual(splitPatchsetGroups(change), [new Set([1])]); }); test('getDateFromTimestamp formats timestamps correctly', () => { assert.strictEqual( - getDateFromTimestamp('2017-12-15T01:30:15.05Z').getTime(), + getDateFromTimestamp('2017-12-15T01:30:15.05Z')!.getTime(), new Date(Date.UTC(2017, 11, 15, 1, 30, 15, 50)).getTime(), ); assert.strictEqual( - getDateFromTimestamp('2017-01-15T01:30:15Z').getTime(), + getDateFromTimestamp('2017-01-15T01:30:15Z')!.getTime(), new Date(Date.UTC(2017, 0, 15, 1, 30, 15, 0)).getTime(), ); assert.strictEqual( - getDateFromTimestamp('2017-02-15T01:30:15.98765432Z').getTime(), + getDateFromTimestamp('2017-02-15T01:30:15.98765432Z')!.getTime(), new Date(Date.UTC(2017, 1, 15, 1, 30, 15, 988)).getTime(), ); });
diff --git a/web/test/cache-object_test.ts b/web/test/cache-object_test.ts index 35eaa52..922786e 100644 --- a/web/test/cache-object_test.ts +++ b/web/test/cache-object_test.ts
@@ -1,7 +1,7 @@ import {CacheObject} from '../cache-object.js'; suite('CacheObject', () => { - let cacheObj; + let cacheObj: CacheObject; setup(() => { cacheObj = new CacheObject('cacheObjectTest');
diff --git a/web/test/checks-fetcher_test.ts b/web/test/checks-fetcher_test.ts index e39bb6a..d9ac08f 100644 --- a/web/test/checks-fetcher_test.ts +++ b/web/test/checks-fetcher_test.ts
@@ -8,29 +8,45 @@ ResultDbV1Client, TestVariantStatus, TestStatus, + TestVariant, } from '../resultdb-client'; import { ChecksFetcher, RunStatus, Attempt, + Build, } from '../checks-fetcher'; import {PluginApi} from '@gerritcodereview/typescript-api/plugin'; -import {Category, LinkIcon} from '@gerritcodereview/typescript-api/checks'; +import {Category, ChangeData, LinkIcon} from '@gerritcodereview/typescript-api/checks'; +import { + AccountInfo, + BranchName, + ChangeId, + ChangeInfo, + ChangeInfoId, + ChangeStatus, + GitRef, + NumericChangeId, + PatchSetNum, + RepoName, + RevisionKind, + Timestamp +} from '@gerritcodereview/typescript-api/rest-api'; suite('checks-fetcher tests', () => { - let fetcher; - let sandbox; - let plugin; - let changeObject; + let fetcher: ChecksFetcher; + let sandbox: sinon.SinonSandbox; + let plugin: PluginApi; + let changeObject: any; setup(() => { sandbox = sinon.createSandbox(); sandbox.stub(BuildbucketV2Client.prototype, 'getAuthorizationHeader') - .returns('accessToken'); + .returns(new Promise((resolve: any, _) => resolve({access_token: 'accessToken', expires_at: 0}))); sandbox.stub(ResultDbV1Client.prototype, 'getAuthorizationHeader') - .returns('accessToken'); + .returns(new Promise((resolve: any, _) => resolve({access_token: 'accessToken', expires_at: 0}))); sandbox.stub(ChecksFetcher.prototype, 'getAuthorizationHeader') - .returns('accessToken'); + .returns(new Promise((resolve: any, _) => resolve({access_token: 'accessToken', expires_at: 0}))); plugin = { restApi: () => ({ get: () => ({ @@ -39,7 +55,7 @@ }), }), getPluginName: () => 'buildbucket', - }; + } as unknown as PluginApi; fetcher = new ChecksFetcher(plugin, 'buildbucket.example.com', 2); fetcher.retryEnabled = true; changeObject = { @@ -47,8 +63,8 @@ patchsetNumber: 1, repo: 'foo/bar', changeInfo: { - status: 'NEW', - revisions: [{kind: 'REWORK', _number: 1}], + status: ChangeStatus.NEW, + revisions: [{kind: RevisionKind.REWORK, _number: 1}], }, }; }); @@ -58,24 +74,24 @@ sandbox.restore(); }); - function stubFetch(response) { + function stubFetch(response: any) { sandbox.stub(window, 'fetch').returns(Promise.resolve({ ok: true, text: async () => `)]}'${JSON.stringify(response)}`, - })); + } as Response)); } test('fetchTestVariants gets an empty list for no TestVariants', async () => { stubFetch({nextPageToken: 'foo'}); - const build = { + const build: Build = { infra: { resultdb: { hostname: 'resultdb.example.com', invocation: '123456', }, }, - }; + } as Build; assert.deepEqual( await fetcher.fetchTestVariants(build, false, 123456), [], @@ -84,9 +100,9 @@ test('fetchTestVariants can query successful builds', async () => { - const variant = {testId: 'foo'}; + const variant = {testId: 'foo'} as TestVariant; stubFetch({testVariants: [variant]}); - const build = { + const build: Build = { status: BuildStatus.SUCCESS, infra: { resultdb: { @@ -94,7 +110,7 @@ invocation: '123456', }, }, - }; + } as Build; assert.deepEqual( await fetcher.fetchTestVariants(build, false, 123456), [], @@ -114,12 +130,12 @@ {status: TestVariantStatus.UNEXPECTED}, ], }); - const build = {infra: {resultdb: {hostname: 'host', invocation: 'inv'}}}; + const build = {infra: {resultdb: {hostname: 'host', invocation: 'inv'}}} as Build; assert.deepEqual( await fetcher.fetchTestVariants(build, false, 123456), [ - {status: TestVariantStatus.UNEXPECTED}, - {status: TestVariantStatus.UNEXPECTED}, + {status: TestVariantStatus.UNEXPECTED} as TestVariant, + {status: TestVariantStatus.UNEXPECTED} as TestVariant, ], ); sinon.assert.calledOnce((fetch as any)); @@ -130,14 +146,30 @@ responses: [ { searchBuilds: { - builds: [{id: 1, tags:[]}], + builds: [{ + id: '1', + tags:[], + builder: {}, + status: BuildStatus.STARTED, + createTime: '', + startTime: '', + endTime: '', + }], nextPageToken: 'foo', }, }, {searchBuilds: {}}, { searchBuilds: { - builds: [{id: 3, tags:[]}], + builds: [{ + id: '3', + tags:[], + builder: {}, + status: BuildStatus.STARTED, + createTime: '', + startTime: '', + endTime: '', + }], nextPageToken: 'bar', }, }, @@ -147,7 +179,15 @@ responses: [ { searchBuilds: { - builds: [{id: 2, tags:[]}], + builds: [{ + id: '2', + tags:[], + builder: {}, + status: BuildStatus.STARTED, + createTime: '', + startTime: '', + endTime: '', + }], nextPageToken: 'baz', }, }, @@ -160,19 +200,47 @@ fetch.onCall(0).returns(Promise.resolve({ ok: true, text: async () => `)]}'${JSON.stringify(firstResponse)}`, - })); + } as Response)); fetch.onCall(1).returns(Promise.resolve({ ok: true, text: async () => `)]}'${JSON.stringify(secondResponse)}`, - })); + } as Response)); fetch.onCall(2).returns(Promise.resolve({ ok: true, text: async () => `)]}'${JSON.stringify(thirdResponse)}`, - })); + } as Response)); assert.deepEqual( - await fetcher.fetchBuilds(123456, [1, 2, 3]), - [{id: 1, tags:[]}, {id: 3, tags:[]}, {id: 2, tags:[]}], + await fetcher.fetchBuilds(123456, [1, 2, 3], '', false, new AbortController()), + [ + { + id: '1', + tags:[], + builder: {}, + status: BuildStatus.STARTED, + createTime: '', + startTime: '', + endTime: '', + }, + { + id: '3', + tags:[], + builder: {}, + status: BuildStatus.STARTED, + createTime: '', + startTime: '', + endTime: '', + }, + { + id: '2', + tags:[], + builder: {}, + status: BuildStatus.STARTED, + createTime: '', + startTime: '', + endTime: '', + } + ], ); sinon.assert.calledThrice(fetch); }); @@ -185,15 +253,39 @@ { searchBuilds: { builds: [ - {id: 1, tags: [nonExpTag]}, + { + id: '1', + tags: [nonExpTag], + builder: {}, + status: BuildStatus.STARTED, + createTime: '', + startTime: '', + endTime: '', + }, ] }, }, { searchBuilds: { builds: [ - {id: 2, tags: [expTag]}, - {id: 3, tags: [nonExpTag]}, + { + id: '2', + tags: [expTag], + builder: {}, + status: BuildStatus.STARTED, + createTime: '', + startTime: '', + endTime: '', + }, + { + id: '3', + tags: [nonExpTag], + builder: {}, + status: BuildStatus.STARTED, + createTime: '', + startTime: '', + endTime: '', + }, ] }, }, @@ -201,19 +293,59 @@ }); assert.deepEqual( - await fetcher.fetchBuilds(123456, [1, 2], 'project', false), + await fetcher.fetchBuilds(123456, [1, 2], 'project', false, new AbortController()), [ - {id: 1, tags: [nonExpTag]}, - {id: 3, tags: [nonExpTag]}, + { + id: '1', + tags: [nonExpTag], + builder: {}, + status: BuildStatus.STARTED, + createTime: '', + startTime: '', + endTime: '', + }, + { + id: '3', + tags: [nonExpTag], + builder: {}, + status: BuildStatus.STARTED, + createTime: '', + startTime: '', + endTime: '', + }, ], ); assert.deepEqual( - await fetcher.fetchBuilds(123456, [1, 2], 'project', true), + await fetcher.fetchBuilds(123456, [1, 2], 'project', true, new AbortController()), [ - {id: 1, tags: [nonExpTag]}, - {id: 2, tags: [expTag]}, - {id: 3, tags: [nonExpTag]}, + { + id: '1', + tags: [nonExpTag], + builder: {}, + status: BuildStatus.STARTED, + createTime: '', + startTime: '', + endTime: '', + }, + { + id: '2', + tags: [expTag], + builder: {}, + status: BuildStatus.STARTED, + createTime: '', + startTime: '', + endTime: '', + }, + { + id: '3', + tags: [nonExpTag], + builder: {}, + status: BuildStatus.STARTED, + createTime: '', + startTime: '', + endTime: '', + }, ], ); }); @@ -226,22 +358,54 @@ { searchBuilds: { builds: [ - {id: 1, tags: [nonHiddenTag]}, + { + id: '1', + tags: [nonHiddenTag], + builder: {}, + status: BuildStatus.STARTED, + createTime: '', + startTime: '', + endTime: '', + }, ] }, }, { searchBuilds: { builds: [ - {id: 2, tags: [hiddenTag]}, - {id: 3, tags: [nonHiddenTag]}, + { + id: '2', + tags: [hiddenTag], + builder: {}, + status: BuildStatus.STARTED, + createTime: '', + startTime: '', + endTime: '', + }, + { + id: '3', + tags: [nonHiddenTag], + builder: {}, + status: BuildStatus.STARTED, + createTime: '', + startTime: '', + endTime: '', + }, ] }, }, { searchBuilds: { builds: [ - {id: 4, tags: [hiddenTag]}, + { + id: '4', + tags: [hiddenTag], + builder: {}, + status: BuildStatus.STARTED, + createTime: '', + startTime: '', + endTime: '', + }, ] }, }, @@ -249,86 +413,183 @@ }); assert.deepEqual( - await fetcher.fetchBuilds(123456, [1, 2], 'project'), + await fetcher.fetchBuilds(123456, [1, 2], 'project', true, new AbortController()), [ - {id: 1, tags: [nonHiddenTag]}, - {id: 3, tags: [nonHiddenTag]}, + { + id: '1', + tags: [nonHiddenTag], + builder: {}, + status: BuildStatus.STARTED, + createTime: '', + startTime: '', + endTime: '', + }, + { + id: '3', + tags: [nonHiddenTag], + builder: {}, + status: BuildStatus.STARTED, + createTime: '', + startTime: '', + endTime: '', + }, ], ); }); test('fetchDisplayedBuilds handles equivalent patchsets', async () => { const stub = sandbox.stub(ChecksFetcher.prototype, 'fetchBuilds'); - const build5 = {id: 5, tags: [{key: 'cq_experimental', value: 'false'}]}; - const build3 = {id: 3, tags: [{key: 'cq_experimental', value: 'false'}]}; - const build2 = {id: 2, tags: [{key: 'cq_experimental', value: 'false'}]}; + const build5: Build = { + id: '5', + tags: [{key: 'cq_experimental', value: 'false'}], + builder: {}, + status: BuildStatus.STARTED, + createTime: '', + startTime: '', + endTime: '', + }; + const build3: Build = { + id: '3', + tags: [{key: 'cq_experimental', value: 'false'}], + builder: {}, + status: BuildStatus.STARTED, + createTime: '', + startTime: '', + endTime: '', + }; + const build2: Build = { + id: '2', + tags: [{key: 'cq_experimental', value: 'false'}], + builder: {}, + status: BuildStatus.STARTED, + createTime: '', + startTime: '', + endTime: '', + }; - stub.withArgs(123, [6], 'project').returns([]); - stub.withArgs(123, [5], 'project').returns([build5]); - stub.withArgs(123, [2, 3], 'project').returns([build2, build3]); - stub.withArgs(123, [2], 'project').returns([build2]); + stub.withArgs(123, [6], 'project', sinon.match.any, sinon.match.any).returns(Promise.resolve([])); + stub.withArgs(123, [5], 'project', sinon.match.any, sinon.match.any).returns(Promise.resolve([build5])); + stub.withArgs(123, [2, 3], 'project', sinon.match.any, sinon.match.any).returns(Promise.resolve([build2, build3])); + stub.withArgs(123, [2], 'project', sinon.match.any, sinon.match.any).returns(Promise.resolve([build2])); - const changeInfo = { - revisions: [ - {kind: 'REWORK', _number: 6}, - {kind: 'REWORK', _number: 5}, - {kind: 'TRIVIAL_REBASE', _number: 4}, - {kind: 'NO_CHANGE', _number: 3}, - {kind: 'REWORK', _number: 2}, - {kind: 'REWORK', _number: 1}, - ], - status: 'MERGED', + const changeInfo: ChangeInfo = { + revisions: { + '1': { + kind: RevisionKind.REWORK, + _number: 6 as PatchSetNum, + created: '' as Timestamp, + uploader: '' as AccountInfo, + ref: '' as GitRef, + }, + '2': { + kind: RevisionKind.REWORK, + _number: 5 as PatchSetNum, + created: '' as Timestamp, + uploader: '' as AccountInfo, + ref: '' as GitRef, + }, + '3': { + kind: RevisionKind.TRIVIAL_REBASE, + _number: 4 as PatchSetNum, + created: '' as Timestamp, + uploader: '' as AccountInfo, + ref: '' as GitRef, + }, + '4': { + kind: RevisionKind.NO_CHANGE, + _number: 3 as PatchSetNum, + created: '' as Timestamp, + uploader: '' as AccountInfo, + ref: '' as GitRef, + }, + '5': { + kind: RevisionKind.REWORK, + _number: 2 as PatchSetNum, + created: '' as Timestamp, + uploader: '' as AccountInfo, + ref: '' as GitRef, + }, + '6': { + kind: RevisionKind.REWORK, + _number: 1 as PatchSetNum, + created: '' as Timestamp, + uploader: '' as AccountInfo, + ref: '' as GitRef, + }, + }, + status: ChangeStatus.MERGED, + id: '' as ChangeInfoId, + project: '' as RepoName, + branch: '' as BranchName, + change_id: '' as ChangeId, + subject: '', + created: '' as Timestamp, + updated: '' as Timestamp, + insertions: 0, + deletions: 0, + _number: 0 as NumericChangeId, + owner: '' as AccountInfo, + reviewers: {}, }; fetcher = new ChecksFetcher(plugin, 'buildbucket.example.com', 2); // Fetching PS 3 gets builds 2 and 3. assert.deepEqual( - await fetcher.fetchDisplayedBuilds(123, 3, 'project', changeInfo), + await fetcher.fetchDisplayedBuilds(123, 3, 'project', changeInfo, new AbortController()), [build2, build3], ) // Fetching PS 2 gets build 2. assert.deepEqual( - await fetcher.fetchDisplayedBuilds(123, 2, 'project', changeInfo), + await fetcher.fetchDisplayedBuilds(123, 2, 'project', changeInfo, new AbortController()), [build2], ) // Fetching PS 6 gets build 5. assert.deepEqual( - await fetcher.fetchDisplayedBuilds(123, 6, 'project', changeInfo), + await fetcher.fetchDisplayedBuilds(123, 6, 'project', changeInfo, new AbortController()), [build5], ) }); test('convertBuildToRun creates no results for SCHEDULED or STARTED builds', () => { - const scheduledBuild = { - builder: {builder: 'foo'}, + const scheduledBuild: Build = { + builder: { builder: 'foo' }, id: '12345', status: BuildStatus.SCHEDULED, + createTime: '', + startTime: '', + endTime: '' }; assert.deepEqual( fetcher.convertBuildToRun( - 123456, scheduledBuild, 1, 1, [{id: '123', status: BuildStatus.SCHEDULED}]).results, + 123456, scheduledBuild, 1, 1, [{id: '123', status: BuildStatus.SCHEDULED, isQuickRun: false, build: scheduledBuild}], '').results, []); - const startedBuild = { - builder: {builder: 'foo'}, + const startedBuild: Build = { + builder: { builder: 'foo' }, id: '12345', status: BuildStatus.STARTED, + createTime: '', + startTime: '', + endTime: '' }; assert.deepEqual( fetcher.convertBuildToRun( - 123456, startedBuild, 1, 1, [{id: '123', status: BuildStatus.STARTED}]).results, + 123456, startedBuild, 1, 1, [{id: '123', status: BuildStatus.STARTED, isQuickRun: false, build: startedBuild}], '').results, []); }); test('convertBuildToRun creates statuses for SCHEDULED and STARTED builds', () => { - const scheduledBuild = { - builder: {builder: 'foo'}, + const scheduledBuild: Build = { + builder: { builder: 'foo' }, id: '12345', status: BuildStatus.SCHEDULED, + createTime: '', + startTime: '', + endTime: '' }; assert.deepEqual( fetcher.convertBuildToRun( @@ -336,14 +597,18 @@ scheduledBuild, 1, 1, - [{id: '123', status: BuildStatus.SCHEDULED}], + [{id: '123', status: BuildStatus.SCHEDULED, isQuickRun: false, build: scheduledBuild}], + '' ).status, RunStatus.SCHEDULED); - const startedBuild = { - builder: {builder: 'foo'}, + const startedBuild : Build= { + builder: { builder: 'foo' }, id: '12345', status: BuildStatus.STARTED, + createTime: '', + startTime: '', + endTime: '' }; assert.deepEqual( fetcher.convertBuildToRun( @@ -351,142 +616,184 @@ startedBuild, 1, 1, - [{id: '123', status: BuildStatus.STARTED}], + [{id: '123', status: BuildStatus.STARTED, isQuickRun: false, build: scheduledBuild}], + '' ).status, RunStatus.RUNNING); }); test('fetch returns only variant results for SUCCESSful builds', async () => { - sandbox.stub(ChecksFetcher.prototype, 'fetchDisplayedBuilds').returns([{ + sandbox.stub(ChecksFetcher.prototype, 'fetchDisplayedBuilds').returns(Promise.resolve([{ builder: {builder: 'foo'}, id: '123456', endTime: '2017-12-15T01:30:15.05Z', status: BuildStatus.SUCCESS, - infra: {resultdb: {hostname: 'host'}}, - }]); + infra: {resultdb: {hostname: 'host', invocation: ''}}, + createTime: '', + startTime: '' + }])); sandbox.stub(ChecksFetcher.prototype, 'fetchTestVariants') - .returns(Promise.resolve([ - {testMetadata: {name: 'test'}, results: [{result: {}}]} - ])); + .returns(Promise.resolve([{ + testMetadata: {name: 'test'}, + results: [{result: {name: '', tags: []}}], + variant: '', + exonerations: [], + status: TestVariantStatus.EXPECTED + }])); const res = await fetcher.fetch(changeObject); - const {results} = res.runs[0]; - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].summary, + const {results} = res.runs![0]; + assert.strictEqual(results!.length, 1); + assert.strictEqual(results![0].summary, 'Test test failed but the build succeeded.', ); }); test('fetch does not fetch variant results for builds tagged with ' + 'hide-test-results-in-gerrit', async () => { - sandbox.stub(ChecksFetcher.prototype, 'fetchDisplayedBuilds').returns([{ + sandbox.stub(ChecksFetcher.prototype, 'fetchDisplayedBuilds').returns(Promise.resolve([{ builder: {builder: 'foo'}, id: '123456', endTime: '2017-12-15T01:30:15.05Z', status: BuildStatus.FAILURE, - infra: {resultdb: {hostname: 'host'}}, + infra: {resultdb: {hostname: 'host', invocation: ''}}, tags: [{key: 'hide-test-results-in-gerrit', value: 'true'}], - }]); + createTime: '', + startTime: '' + }])); const variantSpy = sandbox.spy( ResultDbV1Client.prototype, 'fetchTestVariants'); const res = await fetcher.fetch(changeObject); - const {results} = res.runs[0]; - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].summary, + const {results} = res.runs![0]; + assert.strictEqual(results!.length, 1); + assert.strictEqual(results![0].summary, 'Build foo failed.', ); sinon.assert.notCalled(variantSpy); }); test('convertBuildToRun gets Buildbucket CheckResults', () => { - const build = { - builder: {builder: 'foo'}, + const build : Build= { + builder: { builder: 'foo' }, infra: {}, id: '123456', status: BuildStatus.FAILURE, + createTime: '', + startTime: '', + endTime: '' }; const run = fetcher.convertBuildToRun( - 123456, build, 1, 1, [{id: '123456', status: BuildStatus.FAILURE}]); - assert.strictEqual(run.results.length, 1); - assert.strictEqual(run.results[0].summary, 'Build foo failed.'); + 123456, build, 1, 1, [{id: '123456', status: BuildStatus.FAILURE, isQuickRun: false, build: build}], ''); + assert.strictEqual(run.results!.length, 1); + assert.strictEqual(run.results![0].summary, 'Build foo failed.'); }); test('fetch orders Buildbucket CheckResults first', async () => { - sandbox.stub(ChecksFetcher.prototype, 'fetchDisplayedBuilds').returns([{ + sandbox.stub(ChecksFetcher.prototype, 'fetchDisplayedBuilds').returns(Promise.resolve([{ builder: {builder: 'foo'}, id: '123456', infra: {resultdb: {hostname: 'host', invocation: 'inv'}}, status: BuildStatus.FAILURE, - }]); + createTime: '', + startTime: '', + endTime: '' + }])); sandbox.stub(ChecksFetcher.prototype, 'fetchTestVariants') .returns(Promise.resolve([ { testMetadata: {name: 'test0'}, - results: [{result: {}}], + results: [{result: {name: '', tags: []}}], + variant: '', + exonerations: [], + status: TestVariantStatus.EXPECTED }, { testMetadata: {name: 'test1'}, - results: [{result: {}}], + results: [{result: {name: '', tags: []}}], + variant: '', + exonerations: [], + status: TestVariantStatus.EXPECTED }, ])); const res = await fetcher.fetch(changeObject); - const {results} = res.runs[0]; - assert.strictEqual(results.length, 4); - assert.strictEqual(results[0].summary, 'Build foo failed.'); - assert.strictEqual(results[3].message, + const {results} = res.runs![0]; + assert.strictEqual(results!.length, 4); + assert.strictEqual(results![0].summary, 'Build foo failed.'); + assert.strictEqual(results![3].message, 'More tests may have failed. View all results on the build page.'); }); test('convertBuildToRun handles non-critical builds', () => { - const build = { - builder: {builder: 'foo'}, + const build: Build = { + builder: { builder: 'foo' }, critical: 'NO', infra: {}, id: '123456', status: BuildStatus.FAILURE, + createTime: '', + startTime: '', + endTime: '' }; const run = fetcher.convertBuildToRun( - 123456, build, 1, 1, [{id: '123456', status: BuildStatus.FAILURE, build}]); - assert.strictEqual(run.results.length, 1); - assert.strictEqual(run.results[0].category, Category.WARNING); + 123456, build, 1, 1, [{id: '123456', status: BuildStatus.FAILURE, build, isQuickRun: false}], ''); + assert.strictEqual(run.results!.length, 1); + assert.strictEqual(run.results![0].category, Category.WARNING); assert.strictEqual( - run.results[0].summary, + run.results![0].summary, 'Non-critical build foo failed.', ); }); test('convertBuildToRun sets CheckName based on latest attempt', () => { - const build = { - builder: {builder: 'foo'}, + const build: Build = { + builder: { builder: 'foo' }, critical: 'NO', infra: {}, id: '123456', status: BuildStatus.FAILURE, + createTime: '', + startTime: '', + endTime: '' }; const startedRun = fetcher.convertBuildToRun(123456, build, 1, 1, [ - {id: '1', status: BuildStatus.FAILURE}, - {id: '2', status: BuildStatus.FAILURE}, - {id: '3', status: BuildStatus.STARTED}, - ]); + {id: '1', status: BuildStatus.FAILURE, isQuickRun: false, build}, + {id: '2', status: BuildStatus.FAILURE, isQuickRun: false, build}, + {id: '3', status: BuildStatus.STARTED, isQuickRun: false, build}, + ], ''); assert.strictEqual(startedRun.checkName, 'foo'); const infraFailedRun = fetcher.convertBuildToRun(123456, build, 2, 2, [ - {id: '1', status: BuildStatus.FAILURE}, - {id: '2', status: BuildStatus.INFRA_FAILURE}, - {id: '3', status: BuildStatus.FAILURE}, - ]); + {id: '1', status: BuildStatus.FAILURE, isQuickRun: false, build}, + {id: '2', status: BuildStatus.INFRA_FAILURE, isQuickRun: false, build}, + {id: '3', status: BuildStatus.FAILURE, isQuickRun: false, build}, + ], ''); assert.strictEqual(infraFailedRun.checkName, 'foo'); }); test('convertBuildToRun shows previous attempts', () => { fetcher.showPreviousAttempts = true; - const attempts = [ - {id: '3', build: {id: '3', status: BuildStatus.INFRA_FAILURE}}, - {id: '2', build: {id: '2', status: BuildStatus.SUCCESS}}, - {id: '1', build: {id: '1', status: BuildStatus.FAILURE}}, + const attempts: Attempt[] = [ + { + id: '3', + build: {id: '3', status: BuildStatus.INFRA_FAILURE, builder: {}, createTime: '', startTime: '', endTime: ''}, + isQuickRun: false, + status: BuildStatus.INFRA_FAILURE + }, + { + id: '2', + build: {id: '2', status: BuildStatus.SUCCESS, builder: {}, createTime: '', startTime: '', endTime: ''}, + isQuickRun: false, + status: BuildStatus.SUCCESS + }, + { + id: '1', + build: {id: '1', status: BuildStatus.FAILURE, builder: {}, createTime: '', startTime: '', endTime: ''}, + isQuickRun: false, + status: BuildStatus.FAILURE + }, ]; let run = fetcher.convertBuildToRun( @@ -497,12 +804,15 @@ infra: {}, id: '1', status: BuildStatus.FAILURE, - }, 1, 1, attempts); - assert.strictEqual(run.results.length, 3); + createTime: '', + startTime: '', + endTime: '' + }, 1, 1, attempts, ''); + assert.strictEqual(run.results!.length, 3); // Order is reversed. Show latest results first. - assert.strictEqual(run.results[0].summary, 'Attempt 3 failed.'); - assert.strictEqual(run.results[1].summary, 'Attempt 2 succeeded.'); - assert.strictEqual(run.results[2].summary, 'Attempt 1 infra failed.'); + assert.strictEqual(run.results![0].summary, 'Attempt 3 failed.'); + assert.strictEqual(run.results![1].summary, 'Attempt 2 succeeded.'); + assert.strictEqual(run.results![2].summary, 'Attempt 1 infra failed.'); // Attempts shown should be <= current build. run = fetcher.convertBuildToRun( @@ -513,9 +823,12 @@ infra: {}, id: '3', status: BuildStatus.INFRA_FAILURE, - }, 1, 1, attempts); - assert.strictEqual(run.results.length, 1); - assert.strictEqual(run.results[0].summary, 'Attempt 1 infra failed.'); + createTime: '', + startTime: '', + endTime: '' + }, 1, 1, attempts, ''); + assert.strictEqual(run.results!.length, 1); + assert.strictEqual(run.results![0].summary, 'Attempt 1 infra failed.'); }); test('convertBuildToRun uses max status if showing previous attempts', @@ -530,6 +843,9 @@ infra: {}, id: '1', status: BuildStatus.SUCCESS, + createTime: '', + startTime: '', + endTime: '' }, 1, 1, @@ -537,22 +853,25 @@ { id: '3', status: BuildStatus.FAILURE, - build: {id: '3', status: BuildStatus.FAILURE}, + build: {id: '3', status: BuildStatus.FAILURE, createTime: '', startTime: '', endTime: '', builder: {}}, + isQuickRun: false, }, { id: '2', status: BuildStatus.CANCELED, - build: {id: '2', status: BuildStatus.CANCELED}, + build: {id: '2', status: BuildStatus.CANCELED, createTime: '', startTime: '', endTime: '', builder: {}}, + isQuickRun: false, }, { id: '1', status: BuildStatus.SUCCESS, - build: {id: '1', status: BuildStatus.SUCCESS}, + build: {id: '1', status: BuildStatus.SUCCESS, createTime: '', startTime: '', endTime: '', builder: {}}, + isQuickRun: false, }, - ], + ], '' ); - assert.strictEqual(run.results.length, 3); - for (const res of run.results) { + assert.strictEqual(run.results!.length, 3); + for (const res of run.results!) { assert.strictEqual(res.category, Category.ERROR); } @@ -564,6 +883,9 @@ infra: {}, id: '1', status: BuildStatus.SUCCESS, + createTime: '', + startTime: '', + endTime: '' }, 1, 1, @@ -571,21 +893,25 @@ { id: '3', status: BuildStatus.FAILURE, - build: {id: '3', status: BuildStatus.FAILURE, critical: 'NO'}}, + build: {id: '3', status: BuildStatus.FAILURE, critical: 'NO', createTime: '', startTime: '', endTime: '', builder: {}}, + isQuickRun: false, + }, { id: '2', status: BuildStatus.CANCELED, - build: {id: '2', status: BuildStatus.CANCELED}, + build: {id: '2', status: BuildStatus.CANCELED, createTime: '', startTime: '', endTime: '', builder: {}}, + isQuickRun: false, }, { id: '1', status: BuildStatus.SUCCESS, - build: {id: '1', status: BuildStatus.SUCCESS}, + build: {id: '1', status: BuildStatus.SUCCESS, createTime: '', startTime: '', endTime: '', builder: {}}, + isQuickRun: false, }, - ], + ], '' ); - assert.strictEqual(run.results.length, 3); - for (const res of run.results) { + assert.strictEqual(run.results!.length, 3); + for (const res of run.results!) { assert.strictEqual(res.category, Category.WARNING); } @@ -597,6 +923,9 @@ infra: {}, id: '1', status: BuildStatus.SUCCESS, + createTime: '', + startTime: '', + endTime: '' }, 1, 1, @@ -604,22 +933,25 @@ { id: '3', status: BuildStatus.SUCCESS, - build: {id: '3', status: BuildStatus.SUCCESS}, + build: {id: '3', status: BuildStatus.SUCCESS, createTime: '', startTime: '', endTime: '', builder: {}}, + isQuickRun: false, }, { id: '2', status: BuildStatus.CANCELED, - build: {id: '2', status: BuildStatus.CANCELED}, + build: {id: '2', status: BuildStatus.CANCELED, createTime: '', startTime: '', endTime: '', builder: {}}, + isQuickRun: false, }, { id: '1', status: BuildStatus.SUCCESS, - build: {id: '1', status: BuildStatus.SUCCESS}, + build: {id: '1', status: BuildStatus.SUCCESS, createTime: '', startTime: '', endTime: '', builder: {}}, + isQuickRun: false, }, - ], + ], '' ); - assert.strictEqual(run.results.length, 3); - for (const res of run.results) { + assert.strictEqual(run.results!.length, 3); + for (const res of run.results!) { assert.strictEqual(res.category, Category.INFO); } @@ -631,6 +963,9 @@ infra: {}, id: '1', status: BuildStatus.SUCCESS, + createTime: '', + startTime: '', + endTime: '' }, 1, 1, @@ -638,21 +973,24 @@ { id: '3', status: BuildStatus.SUCCESS, - build: {id: '3', status: BuildStatus.SUCCESS}, + build: {id: '3', status: BuildStatus.SUCCESS, createTime: '', startTime: '', endTime: '', builder: {}}, + isQuickRun: false, }, { id: '2', status: BuildStatus.SUCCESS, - build: {id: '2', status: BuildStatus.SUCCESS}, + build: {id: '2', status: BuildStatus.SUCCESS, createTime: '', startTime: '', endTime: '', builder: {}}, + isQuickRun: false, }, { id: '1', status: BuildStatus.SUCCESS, - build: {id: '1', status: BuildStatus.SUCCESS}, + build: {id: '1', status: BuildStatus.SUCCESS, createTime: '', startTime: '', endTime: '', builder: {}}, + isQuickRun: false, }, - ], + ], '' ); - assert.strictEqual(run.results.length, 0); + assert.strictEqual(run.results!.length, 0); }); test('convertBuildToRun uses latest status if not showing previous attempts', @@ -667,6 +1005,9 @@ infra: {}, id: '1', status: BuildStatus.SUCCESS, + createTime: '', + startTime: '', + endTime: '' }, 1, 1, @@ -674,21 +1015,24 @@ { id: '3', status: BuildStatus.FAILURE, - build: {id: '3', status: BuildStatus.FAILURE}, + build: {id: '3', status: BuildStatus.FAILURE, createTime: '', startTime: '', endTime: '', builder: {}}, + isQuickRun: false, }, { id: '2', status: BuildStatus.CANCELED, - build: {id: '2', status: BuildStatus.CANCELED}, + build: {id: '2', status: BuildStatus.CANCELED, createTime: '', startTime: '', endTime: '', builder: {}}, + isQuickRun: false, }, { id: '1', status: BuildStatus.SUCCESS, - build: {id: '1', status: BuildStatus.SUCCESS}, + build: {id: '1', status: BuildStatus.SUCCESS, createTime: '', startTime: '', endTime: '', builder: {}}, + isQuickRun: false, }, - ], + ], '' ); - assert.strictEqual(run.results.length, 0); + assert.strictEqual(run.results!.length, 0); // Canceled runs should have a result. run = fetcher.convertBuildToRun( @@ -698,6 +1042,9 @@ infra: {}, id: '1', status: BuildStatus.CANCELED, + createTime: '', + startTime: '', + endTime: '' }, 1, 1, @@ -705,27 +1052,30 @@ { id: '3', status: BuildStatus.FAILURE, - build: {id: '3', status: BuildStatus.FAILURE}, + build: {id: '3', status: BuildStatus.FAILURE, createTime: '', startTime: '', endTime: '', builder: {}}, + isQuickRun: false, }, { id: '2', status: BuildStatus.INFRA_FAILURE, - build: {id: '2', status: BuildStatus.INFRA_FAILURE}, + build: {id: '2', status: BuildStatus.INFRA_FAILURE, createTime: '', startTime: '', endTime: '', builder: {}}, + isQuickRun: false, }, { id: '1', status: BuildStatus.CANCELED, - build: {id: '1', status: BuildStatus.CANCELED}, + build: {id: '1', status: BuildStatus.CANCELED, createTime: '', startTime: '', endTime: '', builder: {}}, + isQuickRun: false, }, - ], + ], '' ); - assert.strictEqual(run.results.length, 1); - assert.strictEqual(run.results[0].category, Category.INFO); + assert.strictEqual(run.results!.length, 1); + assert.strictEqual(run.results![0].category, Category.INFO); }); test('convertBuildToRun handles RTS builds', () => { - const build = { - builder: {builder: 'foo'}, + const build: Build = { + builder: { builder: 'foo' }, critical: 'NO', infra: {}, id: '1', @@ -734,88 +1084,102 @@ properties: { rts_was_used: true, } - } + }, + createTime: '', + startTime: '', + endTime: '' }; let run = fetcher.convertBuildToRun(123456, build, 1, 1, [ - {id: '1', status: BuildStatus.INFRA_FAILURE, isQuickRun: true}, - ]); + {id: '1', status: BuildStatus.INFRA_FAILURE, isQuickRun: true, build}, + ], ''); assert.strictEqual(run.checkName, '🟪\u2005\u2005🚀\u2005\u2005foo'); - assert.isTrue(run.statusDescription.includes('Quick Run')); - assert.isTrue(run.results[0].tags.map(t => t.name) + assert.isTrue(run.statusDescription!.includes('Quick Run')); + assert.isTrue(run.results![0].tags!.map(t => t.name) .includes('🚀\u2005\u2005Quick Run')); // The last attempt should be used for check run naming. run = fetcher.convertBuildToRun(123456, build, 1, 1, [ - {id: '2', status: BuildStatus.INFRA_FAILURE, isQuickRun: true}, - {id: '1', status: BuildStatus.INFRA_FAILURE, isQuickRun: false}, - ]); + {id: '2', status: BuildStatus.INFRA_FAILURE, isQuickRun: true, build}, + {id: '1', status: BuildStatus.INFRA_FAILURE, isQuickRun: false, build}, + ], ''); assert.strictEqual(run.checkName, '🟪\u2005\u2005foo'); }); test('convertBuildToRun tags CheckResults for experimental builds', () => { - const baseBuild = { - builder: {builder: 'foo'}, + const baseBuild: Build = { + builder: { builder: 'foo' }, id: '123456', - infra: {resultdb: {hostname: 'host', invocation: 'inv'}}, + infra: { resultdb: { hostname: 'host', invocation: 'inv' } }, status: BuildStatus.FAILURE, + createTime: '', + startTime: '', + endTime: '' }; - const attempts = [{id: '123456', status: BuildStatus.FAILURE}]; + const attempts: Attempt[] = [{id: '123456', status: BuildStatus.FAILURE, isQuickRun: false, build: baseBuild}]; - const expBuild = Object.assign(baseBuild, + const expBuild: Build = Object.assign(baseBuild, {tags: [{key: 'cq_experimental', value: 'true'}]}); - const expRun = fetcher.convertBuildToRun(123456, expBuild, 1, 1, attempts); - expRun.results.forEach((result) => { + const expRun = fetcher.convertBuildToRun(123456, expBuild, 1, 1, attempts, ''); + expRun.results!.forEach((result) => { assert.isTrue( - result.tags.map(t => t.name).includes('Experimental')); + result.tags!.map(t => t.name).includes('Experimental')); }); const nonExpBuild = Object.assign(baseBuild, {tags: [{key: 'cq_experimental', value: 'false'}]}); - const nonExpRun = fetcher.convertBuildToRun(123456, nonExpBuild, 1, 1, attempts); - nonExpRun.results.forEach((result) => { + const nonExpRun = fetcher.convertBuildToRun(123456, nonExpBuild, 1, 1, attempts, ''); + nonExpRun.results!.forEach((result) => { assert.isFalse( - result.tags.map(t => t.name).includes('Experimental')); + result.tags!.map(t => t.name).includes('Experimental')); }); }); test('convertBuildToRun tags infra failures', () => { - const baseBuild = { + const baseBuild: Build = { builder: {builder: 'foo'}, id: '123456', infra: {resultdb: {hostname: 'host', invocation: 'inv'}}, + status: BuildStatus.FAILURE, + createTime: '', + startTime: '', + endTime: '' }; - const attempts = [{id: '123456', status: BuildStatus.INFRA_FAILURE}]; + const attempts: Attempt[] = [{id: '123456', status: BuildStatus.INFRA_FAILURE, isQuickRun: false, build: baseBuild}]; const infraFailedBuild = Object.assign( baseBuild, {status: BuildStatus.INFRA_FAILURE}); const infraRun = fetcher.convertBuildToRun( - 123456, infraFailedBuild, 1, 1, attempts); + 123456, infraFailedBuild, 1, 1, attempts, ''); assert.strictEqual(infraRun.checkName, '🟪\u2005\u2005foo'); - infraRun.results.forEach((r) => { - assert.isTrue(r.tags.map(t => t.name).includes('Infra Failure')); + infraRun.results!.forEach((r) => { + assert.isTrue(r.tags!.map(t => t.name).includes('Infra Failure')); }); const failedBuild = Object.assign(baseBuild, {status: BuildStatus.FAILURE}); const nonInfraRun = fetcher.convertBuildToRun( - 123456, failedBuild, 1, 1, attempts); - nonInfraRun.results.forEach((r) => { - assert.isFalse(r.tags.map(t => t.name).includes('Infra Failure')); + 123456, failedBuild, 1, 1, attempts, ''); + nonInfraRun.results!.forEach((r) => { + assert.isFalse(r.tags!.map(t => t.name).includes('Infra Failure')); }); }); test('convertBuildToRun categorizes failed test results as WARNING if ' + 'build succeeded', async () => { - const build = { - builder: {builder: 'foo'}, + const build: Build = { + builder: { builder: 'foo' }, id: '123456', endTime: '2017-12-15T01:30:15.05Z', status: BuildStatus.SUCCESS, - infra: {resultdb: {hostname: 'host', invocation: 'inv'}}, + infra: { resultdb: { hostname: 'host', invocation: 'inv' } }, + createTime: '', + startTime: '' }; - const variants = [{ + const variants: TestVariant[] = [{ testMetadata: {name: 'test'}, status: TestVariantStatus.UNEXPECTED, - results: [{result: {status: TestStatus.FAIL, summaryHtml: '<p>foo</p>'}}], + results: [{result: {status: TestStatus.FAIL, summaryHtml: '<p>foo</p>', name: '', tags: []}}], + variant: '', + exonerations: [], }]; const results = await fetcher.convertVariantsToCheckResults(build, variants, 123456); @@ -831,29 +1195,37 @@ }); test('convertVariantsToCheckResults handles flaky results', async () => { - const build = { - builder: {builder: 'foo'}, + const build: Build = { + builder: { builder: 'foo' }, id: '123456', status: BuildStatus.FAILURE, endTime: '2017-12-15T01:30:15.05Z', - infra: {resultdb: {hostname: 'host', invocation: 'inv'}}, + infra: { resultdb: { hostname: 'host', invocation: 'inv' } }, + createTime: '', + startTime: '' }; - const variants = [{ + const variants: TestVariant[] = [{ testMetadata: {name: 'foo-test'}, status: TestVariantStatus.FLAKY, results: [ { result: { status: TestStatus.FAIL, + name: '', + tags: [] }, }, { result: { status: TestStatus.PASS, expected: true, + name: '', + tags: [] }, }, ], + variant: '', + exonerations: [], }]; const results = await fetcher.convertVariantsToCheckResults(build, variants, 123456); @@ -875,17 +1247,21 @@ }); test('convertVariantsToCheckResults handles exonerated results', async () => { - const build = { - builder: {builder: 'foo'}, + const build: Build = { + builder: { builder: 'foo' }, id: '123456', status: BuildStatus.FAILURE, endTime: '2017-12-15T01:30:15.05Z', - infra: {resultdb: {hostname: 'host', invocation: 'inv'}}, + infra: { resultdb: { hostname: 'host', invocation: 'inv' } }, + createTime: '', + startTime: '' }; - const variants = [{ + const variants: TestVariant[] = [{ testMetadata: {name: 'foo-test'}, status: TestVariantStatus.EXONERATED, - results: [{result: {status: TestStatus.PASS}}], + results: [{result: {status: TestStatus.PASS, name: '', tags: []}}], + variant: '', + exonerations: [], }]; const results = await fetcher.convertVariantsToCheckResults(build, variants, 123456); @@ -896,17 +1272,21 @@ }); test('convertVariantsToCheckResults handles unexpectedly skipped results', async () => { - const build = { - builder: {builder: 'foo'}, + const build: Build = { + builder: { builder: 'foo' }, id: '123456', status: BuildStatus.FAILURE, endTime: '2017-12-15T01:30:15.05Z', - infra: {resultdb: {hostname: 'host', invocation: 'inv'}}, + infra: { resultdb: { hostname: 'host', invocation: 'inv' } }, + createTime: '', + startTime: '' }; - const variants = [{ + const variants: TestVariant[] = [{ testMetadata: {name: 'foo-test'}, status: TestVariantStatus.UNEXPECTEDLY_SKIPPED, - results: [{result: {status: TestStatus.SKIP}}], + results: [{result: {status: TestStatus.SKIP, name: '', tags: []}}], + variant: '', + exonerations: [], }]; const results = await fetcher.convertVariantsToCheckResults(build, variants, 123456); @@ -917,22 +1297,29 @@ }); test('convertVariantsToCheckResults categorizes preliminary results', async () => { - const build = { - builder: {builder: 'foo'}, + const build: Build = { + builder: { builder: 'foo' }, id: '123456', status: BuildStatus.STARTED, - infra: {resultdb: {hostname: 'host', invocation: 'inv'}}, + infra: { resultdb: { hostname: 'host', invocation: 'inv' } }, + createTime: '', + startTime: '', + endTime: '' }; - const variants = [ + const variants: TestVariant[] = [ { testMetadata: {name: 'test0'}, status: TestVariantStatus.UNEXPECTED, - results: [{result: {status: TestStatus.FAIL, summaryHtml: '<p>foo</p>'}}], + results: [{result: {status: TestStatus.FAIL, summaryHtml: '<p>foo</p>', name: '', tags: []}}], + variant: '', + exonerations: [], }, { testMetadata: {name: 'test1'}, status: TestVariantStatus.UNEXPECTED, - results: [{result: {status: TestStatus.FAIL, summaryHtml: '<p>foo</p>'}}], + results: [{result: {status: TestStatus.FAIL, summaryHtml: '<p>foo</p>', name: '', tags: []}}], + variant: '', + exonerations: [], }, ]; @@ -945,111 +1332,129 @@ checkResult.summary, 'Test test0 failed.', ); - assert.isTrue(checkResult.tags + assert.isTrue(checkResult.tags! .map(t => t.name).includes('Preliminary')); assert.strictEqual(extraResult.category, Category.INFO); - assert.isTrue(extraResult.tags.map(t => t.name).includes('Preliminary')); + assert.isTrue(extraResult.tags!.map(t => t.name).includes('Preliminary')); }); test('convertVariantsToCheckResults tags results with variant def', async () => { - const build = { - builder: {builder: 'foo'}, + const build: Build = { + builder: { builder: 'foo' }, id: '123456', endTime: '2017-12-15T01:30:15.05Z', status: BuildStatus.FAILURE, - infra: {resultdb: {hostname: 'host', invocation: 'inv'}}, + infra: { resultdb: { hostname: 'host', invocation: 'inv' } }, + createTime: '', + startTime: '' }; - const variants = [ + const variants: TestVariant[] = [ { variant: {def: {test_suite: 'suite_foo', gpu: 'gpu_foo'}}, testMetadata: {name: 'test2'}, status: TestVariantStatus.UNEXPECTED, - results: [{result: {status: TestStatus.FAIL}}], + results: [{result: {status: TestStatus.FAIL, name: '', tags: []}}], + exonerations: [], }, { variant: {def: {test_suite: 'suite_foo', build_target: 'build_foo'}}, testMetadata: {name: 'test1'}, status: TestVariantStatus.UNEXPECTED, - results: [{result: {status: TestStatus.FAIL}}], + results: [{result: {status: TestStatus.FAIL, name: '', tags: []}}], + exonerations: [], }, { variant: {def: {test_suite: 'suite_bar', gpu: 'gpu_bar'}}, testMetadata: {name: 'test1'}, status: TestVariantStatus.UNEXPECTED, - results: [{result: {status: TestStatus.FAIL}}], + results: [{result: {status: TestStatus.FAIL, name: '', tags: []}}], + exonerations: [], }, { variant: {def: {}}, testMetadata: {name: 'test0'}, status: TestVariantStatus.UNEXPECTED, - results: [{result: {status: TestStatus.FAIL}}], + results: [{result: {status: TestStatus.FAIL, name: '', tags: []}}], + exonerations: [], }, ]; const results = await fetcher.convertVariantsToCheckResults(build, variants, 123456); assert.isTrue(results[0].summary.includes('test0')); - assert.strictEqual(results[0].tags.length, 0); + assert.strictEqual(results[0].tags!.length, 0); assert.isTrue(results[1].summary.includes('test1')); - assert.strictEqual(results[1].tags.length, 2); - assert.isTrue(results[1].tags[0].tooltip.includes('suite_bar')); - assert.isTrue(results[1].tags[1].tooltip.includes('gpu_bar')); + assert.strictEqual(results[1].tags!.length, 2); + assert.isTrue(results[1].tags![0].tooltip!.includes('suite_bar')); + assert.isTrue(results[1].tags![1].tooltip!.includes('gpu_bar')); assert.isTrue(results[2].summary.includes('test1')); - assert.strictEqual(results[2].tags.length, 2); - assert.isTrue(results[2].tags[0].tooltip.includes('suite_foo')); - assert.isTrue(results[2].tags[1].tooltip.includes('build_foo')); + assert.strictEqual(results[2].tags!.length, 2); + assert.isTrue(results[2].tags![0].tooltip!.includes('suite_foo')); + assert.isTrue(results[2].tags![1].tooltip!.includes('build_foo')); assert.isTrue(results[3].summary.includes('test2')); - assert.strictEqual(results[3].tags.length, 2); - assert.isTrue(results[3].tags[0].tooltip.includes('suite_foo')); - assert.isTrue(results[3].tags[1].tooltip.includes('gpu_foo')); + assert.strictEqual(results[3].tags!.length, 2); + assert.isTrue(results[3].tags![0].tooltip!.includes('suite_foo')); + assert.isTrue(results[3].tags![1].tooltip!.includes('gpu_foo')); }); test('convertBuildToRun returns correct actions', () => { - const startedBuild = { - builder: {builder: 'foo'}, + const startedBuild: Build = { + builder: { builder: 'foo' }, id: '123', status: BuildStatus.STARTED, - infra: {resultdb: {hostname: 'host', invocation: 'inv'}}, + infra: { resultdb: { hostname: 'host', invocation: 'inv' } }, + createTime: '', + startTime: '', + endTime: '' }; const startedRun = fetcher.convertBuildToRun( - 123456, startedBuild, 1, 1, [{id: '123', status: BuildStatus.STARTED}]); + 123456, startedBuild, 1, 1, [{id: '123', status: BuildStatus.STARTED, isQuickRun: false, build: startedBuild}], ''); assert.strictEqual(startedRun.checkName, 'foo'); - assert.strictEqual(startedRun.actions.length, 1); - assert.strictEqual(startedRun.actions[0].name, 'Cancel'); + assert.strictEqual(startedRun.actions!.length, 1); + assert.strictEqual(startedRun.actions![0].name, 'Cancel'); - const noRetryBuild = { - builder: {builder: 'foo'}, + const noRetryBuild: Build = { + builder: { builder: 'foo' }, id: '123456', status: BuildStatus.FAILURE, - infra: {resultdb: {hostname: 'host', invocation: 'inv'}}, - tags: [{key: 'skip-retry-in-gerrit', value: 'true'}], + infra: { resultdb: { hostname: 'host', invocation: 'inv' } }, + tags: [{ key: 'skip-retry-in-gerrit', value: 'true' }], + createTime: '', + startTime: '', + endTime: '' }; const noRetryRun = fetcher.convertBuildToRun( - 123456, noRetryBuild, 1, 1, [{id: '123', status: BuildStatus.FAILURE}]); + 123456, noRetryBuild, 1, 1, [{id: '123', status: BuildStatus.FAILURE, isQuickRun: false, build: noRetryBuild}], ''); assert.strictEqual(noRetryRun.checkName, 'foo'); - assert.strictEqual(noRetryRun.actions.length, 0); + assert.strictEqual(noRetryRun.actions!.length, 0); }); test('convertBuildToRun returns correct build links', () => { - const successBuild = { - builder: {builder: 'bar', project: 'chromium', bucket: 'baz'}, + const successBuild: Build = { + builder: { builder: 'bar', project: 'chromium', bucket: 'baz' }, id: '123456', summaryMarkdown: 'Build passed', number: 987, status: BuildStatus.SUCCESS, + createTime: '', + startTime: '', + endTime: '' }; - const failedBuild = { - builder: {builder: 'foo', project: 'chromium', bucket: 'baz'}, + const failedBuild: Build = { + builder: { builder: 'foo', project: 'chromium', bucket: 'baz' }, id: '123456', summaryMarkdown: 'Build failed', number: 987, status: BuildStatus.FAILURE, + createTime: '', + startTime: '', + endTime: '' }; const successRun = fetcher.convertBuildToRun( - 123456, successBuild, 1, 1, [{id: '123456', status: BuildStatus.SUCCESS}], []); + 123456, successBuild, 1, 1, [{id: '123456', status: BuildStatus.SUCCESS, isQuickRun: false, build: successBuild}], ''); assert.deepEqual(successRun.results, []); assert.strictEqual( successRun.statusDescription, @@ -1057,11 +1462,11 @@ ); const failedRun = fetcher.convertBuildToRun( - 123456, failedBuild, 1, 1, [{id: '123456', status: BuildStatus.FAILURE}], []); - assert.strictEqual(failedRun.results[0].summary, 'Build foo failed.'); - assert.strictEqual(failedRun.results[0].message, 'Build failed'); + 123456, failedBuild, 1, 1, [{id: '123456', status: BuildStatus.FAILURE, isQuickRun: false, build: failedBuild}], ''); + assert.strictEqual(failedRun.results![0].summary, 'Build foo failed.'); + assert.strictEqual(failedRun.results![0].message, 'Build failed'); assert.deepEqual( - failedRun.results[0].links, + failedRun.results![0].links, [{ url: 'https://buildbucket.example.com/build/123456', tooltip: 'Build', @@ -1073,15 +1478,18 @@ test('convertVariantsToCheckResults returns correct links', async () => { // Build is SUCCESSful and should only contain TestVariant CheckResults. - const build = { - builder: {builder: 'foo', project: 'bar', bucket: 'baz'}, + const build: Build = { + builder: { builder: 'foo', project: 'bar', bucket: 'baz' }, id: '123456', number: 987, status: BuildStatus.SUCCESS, - infra: {resultdb: {hostname: 'host', invocation: 'inv'}}, + infra: { resultdb: { hostname: 'host', invocation: 'inv' } }, + createTime: '', + startTime: '', + endTime: '' }; - const variants = [{ + const variants: TestVariant[] = [{ testMetadata: {name: 'test0'}, status: TestVariantStatus.UNEXPECTED, variantHash: 'deadbeef', @@ -1095,8 +1503,10 @@ ], }, }, - {result: {name: 'this is a bad name'}}, + {result: {name: 'this is a bad name', tags: []}}, ], + exonerations: [], + variant: '' }]; const results = await fetcher.convertVariantsToCheckResults(build, variants, 123456); @@ -1170,44 +1580,47 @@ const stubbedFetcher = new ChecksFetcher(plugin, 'buildbucket.example.com', 2); const nonExpResult = await stubbedFetcher.fetch(changeObject); - assert.strictEqual(nonExpResult.runs[0].results.length, 1); - assert.isFalse(nonExpResult.runs[0].results[0].tags + assert.strictEqual(nonExpResult.runs![0].results!.length, 1); + assert.isFalse(nonExpResult.runs![0].results![0].tags! .map(t => t.name).includes('Experimental')); assert.strictEqual( - nonExpResult.actions[2].name, 'Show Additional Results'); + nonExpResult.actions![2].name, 'Show Additional Results'); // Simulate "Show/Hide Additional" click. - await nonExpResult.actions[2].callback(1, 1, 1, 1, 'name', 'action'); + await nonExpResult.actions![2].callback(1); sinon.assert.calledOnce(update); // CheckResult should contain experimental runs. const expResult = await stubbedFetcher.fetch(changeObject); - assert.strictEqual(expResult.runs.length, 2); - assert.isTrue(expResult.runs[0].results[0].tags + assert.strictEqual(expResult.runs!.length, 2); + assert.isTrue(expResult.runs![0].results![0].tags! .map(t => t.name).includes('Experimental')); - assert.isFalse(expResult.runs[1].results[0].tags + assert.isFalse(expResult.runs![1].results![0].tags! .map(t => t.name).includes('Experimental')); assert.strictEqual( - expResult.actions[2].name, 'Hide Additional Results'); + expResult.actions![2].name, 'Hide Additional Results'); }); test('fetch handles ResultDB failures', async () => { - const builds = [{ + const builds: Build[] = [{ builder: {builder: 'bar'}, id: '421532', infra: {resultdb: {hostname: 'host', invocation: 'inv'}}, status: BuildStatus.FAILURE, + createTime: '', + startTime: '', + endTime: '' }]; sandbox.stub(ChecksFetcher.prototype, 'fetchDisplayedBuilds') - .returns(builds); + .returns(Promise.resolve(builds)); sandbox.stub(ChecksFetcher.prototype, 'fetchTestVariants') .returns(Promise.reject('ResultDB failed!')); // A notice run should be generated. const res = await fetcher.fetch(changeObject); - assert.strictEqual(res.runs.length, 2); - assert.strictEqual(res.runs[1].checkName, 'Notice'); - assert.strictEqual(res.runs[1].results[0].summary, + assert.strictEqual(res.runs!.length, 2); + assert.strictEqual(res.runs![1].checkName, 'Notice'); + assert.strictEqual(res.runs![1].results![0].summary, 'Test results may be missing.'); }); @@ -1232,7 +1645,7 @@ ], }); - assert.deepEqual(await fetcher.retryFailedBuildsCallback(50, 3, 'repo'), { + assert.deepEqual(await fetcher.retryFailedBuildsCallback(50, 3, 'repo', new AbortController()), { message: 'No failed builds to retry', }); }); @@ -1288,7 +1701,7 @@ const stub = sandbox.stub( ChecksFetcher.prototype, 'scheduleBuildsCallback'); - await fetcher.retryFailedBuildsCallback(50, 3, 'repo'); + await fetcher.retryFailedBuildsCallback(50, 3, 'repo', new AbortController()); sinon.assert.calledWith( stub, 50, 3, 'repo', [{ builder: 'bar' }], [RETRY_FAILED_TAG]); }); @@ -1296,9 +1709,9 @@ test('run action returns an error message if failed', async () => { const plugin = { checks: () => { - return {announceUpdate: sandbox.stub()}; + return { announceUpdate: sandbox.stub() }; }, - } as PluginApi; + } as unknown as PluginApi; const stubbedFetcher = new ChecksFetcher(plugin, 'buildbucket.example.com', 2); stubbedFetcher.retryEnabled = true; @@ -1314,6 +1727,9 @@ builder: {builder: 'foo'} as Builder, endTime: '2017-12-15T01:30:15.05Z', status: BuildStatus.FAILURE, + createTime: '', + startTime: '', + }, 'project', 1, @@ -1324,16 +1740,16 @@ } as unknown as Attempt], )[0].callback; - let res = await runAction(123456); + let res = await runAction(123456, 0, undefined, undefined, undefined, ''); assert.strictEqual( - res.message, '1 of 1 cancel requests failed. See console logs.'); + res!.message, '1 of 1 cancel requests failed. See console logs.'); batchStub.onCall(1).returns(Promise.resolve({responses: []})); batchStub.onCall(2).returns( Promise.resolve({responses: [{error: 'bad schedule'}]})); - res = await runAction(123456); + res = await runAction(123456, 0, undefined, undefined, undefined, ''); assert.strictEqual( - res.message, '1 of 1 schedule requests failed. See console logs.'); + res!.message, '1 of 1 schedule requests failed. See console logs.'); }); test('chooseTryjobsCallback opens a tryjob picker', async () => { @@ -1347,7 +1763,7 @@ pluginConfig: {config: 'old'}, change: {_number: 678, project: 'old/project'}, revision: {_number: 9}, - }); + }) as any; const picker = { close: sandbox.stub(), @@ -1363,18 +1779,18 @@ const plugin = { popup: sandbox.stub().returns(picker), restApi: () => { - return {get: sandbox.stub().returns(config)}; + return { get: sandbox.stub().returns(config) }; }, getPluginName: () => 'buildbucket', checks: () => { - return {announceUpdate: sandbox.stub()}; + return { announceUpdate: sandbox.stub() }; }, - } as PluginApi; + } as unknown as PluginApi; const fetcherWithPlugin = new ChecksFetcher(plugin, 'newhost', 2); const result = await fetcherWithPlugin.fetch(changeObject); - assert.strictEqual(result.actions[0].name, 'Choose Tryjobs'); - await result.actions[0].callback(123456, 321); + assert.strictEqual(result.actions![0].name, 'Choose Tryjobs'); + await result.actions![0].callback(123456); sinon.assert.calledOnce(plugin.popup as any); assert.strictEqual(element.buildbucketHost, 'newhost'); @@ -1388,17 +1804,17 @@ stubFetch({responses: [{searchBuilds: {}}]}); const plugin = { restApi: () => { - return {get: sandbox.stub().returns({buckets: []})}; + return { get: sandbox.stub().returns({ buckets: [] }) }; }, getPluginName: () => 'buildbucket', checks: () => { - return {announceUpdate: sandbox.stub()}; + return { announceUpdate: sandbox.stub() }; }, - } as PluginApi; + } as unknown as PluginApi; const fetcherWithPlugin = new ChecksFetcher(plugin, 'newhost', 2); const result = await fetcherWithPlugin.fetch(changeObject); - assert.isFalse(result.actions.some(a => a.name === 'Choose Tryjobs')); + assert.isFalse(result.actions!.some(a => a.name === 'Choose Tryjobs')); }); test('retry failed is hidden if disabled in config', async () => { @@ -1417,18 +1833,18 @@ }); const plugin = { restApi: () => { - return {get: sandbox.stub().returns({hideRetryButton: true})}; + return { get: sandbox.stub().returns({ hideRetryButton: true }) }; }, getPluginName: () => 'buildbucket', checks: () => { - return {announceUpdate: sandbox.stub()}; + return { announceUpdate: sandbox.stub() }; }, - } as PluginApi; + } as unknown as PluginApi; const fetcherWithPlugin = new ChecksFetcher(plugin, 'newhost', 2); const result = await fetcherWithPlugin.fetch(changeObject); - assert.isFalse(result.actions.some(a => a.name === 'Retry Failed Builds')); - assert.strictEqual(result.runs[0].actions.length, 0); + assert.isFalse(result.actions!.some(a => a.name === 'Retry Failed Builds')); + assert.strictEqual(result.runs![0].actions!.length, 0); }); test('show additional results and give feedback actions can be primary', @@ -1436,25 +1852,27 @@ stubFetch({responses: [{searchBuilds: {}}]}); const plugin = { restApi: () => { - return {get: sandbox.stub().returns({ - buckets: [], - hideRetryButton: true, - })}; + return { + get: sandbox.stub().returns({ + buckets: [], + hideRetryButton: true, + }) + }; }, getPluginName: () => 'buildbucket', checks: () => { - return {announceUpdate: sandbox.stub()}; + return { announceUpdate: sandbox.stub() }; }, - } as PluginApi; + } as unknown as PluginApi; const fetcherWithPlugin = new ChecksFetcher(plugin, 'newhost', 2); const result = await fetcherWithPlugin.fetch(changeObject); - assert.strictEqual(result.actions[0].name, 'Show Additional Results'); - assert.isTrue(result.actions[0].primary); - assert.strictEqual(result.actions[1].name, 'Show Previous Attempts'); - assert.isFalse(result.actions[1].primary); - assert.strictEqual(result.actions[2].name, 'Give Feedback'); - assert.isTrue(result.actions[2].primary); + assert.strictEqual(result.actions![0].name, 'Show Additional Results'); + assert.isTrue(result.actions![0].primary); + assert.strictEqual(result.actions![1].name, 'Show Previous Attempts'); + assert.isFalse(result.actions![1].primary); + assert.strictEqual(result.actions![2].name, 'Give Feedback'); + assert.isTrue(result.actions![2].primary); }); test('fetch aborts pending fetch requests', async () => { @@ -1462,7 +1880,7 @@ new Promise((resolve, _) => { const wait = setTimeout(() => { clearTimeout(wait); - resolve('foo'); + resolve('foo' as unknown as Response); }, 9999999); }) ); @@ -1473,19 +1891,19 @@ fetcher.fetch(changeObject); await new Promise(r => setTimeout(r, 100)); const firstController = fetcher.controllers.get(controllerKey); - assert.strictEqual(firstController.signal.aborted, false); + assert.strictEqual(firstController!.signal.aborted, false); // Following fetches should be aborted. fetcher.fetch(changeObject); await new Promise(r => setTimeout(r, 100)); const secondController = fetcher.controllers.get(controllerKey); assert.notStrictEqual(firstController, secondController); - assert.strictEqual(firstController.signal.aborted, true); - assert.strictEqual(secondController.signal.aborted, false); + assert.strictEqual(firstController!.signal.aborted, true); + assert.strictEqual(secondController!.signal.aborted, false); fetcher.fetch(changeObject) await new Promise(r => setTimeout(r, 100)); - assert.strictEqual(secondController.signal.aborted, true); + assert.strictEqual(secondController!.signal.aborted, true); }); test('fetch aborts based on repo, change, and patchset', async () => { @@ -1493,29 +1911,71 @@ new Promise((resolve, _) => { const wait = setTimeout(() => { clearTimeout(wait); - resolve('foo'); + resolve('foo' as unknown as Response); }, 9999999); }) ); - const firstChangeObject = { + const firstChangeObject: ChangeData = { changeNumber: 123456, patchsetNumber: 1, repo: 'foo/bar', changeInfo: { - status: 'NEW', - revisions: [{kind: 'REWORK', _number: 1}], + status: ChangeStatus.NEW, + revisions: { + 1: { + kind: RevisionKind.REWORK, + _number: 1 as PatchSetNum, + created: '' as Timestamp, + uploader: '' as AccountInfo, + ref: '' as GitRef, + }, + }, + id: '' as ChangeInfoId, + project: '' as RepoName, + branch: '' as BranchName, + change_id: '' as ChangeId, + subject: '', + created: '' as Timestamp, + updated: '' as Timestamp, + insertions: 0, + deletions: 0, + _number: 0 as NumericChangeId, + owner: '' as AccountInfo, + reviewers: {}, }, + patchsetSha: '' }; - const secondChangeObject = { + const secondChangeObject: ChangeData = { changeNumber: 123456, patchsetNumber: 2, repo: 'foo/bar', changeInfo: { - status: 'NEW', - revisions: [{kind: 'REWORK', _number: 1}], + status: ChangeStatus.NEW, + revisions: { + 1: { + kind: RevisionKind.REWORK, + _number: 1 as PatchSetNum, + created: '' as Timestamp, + uploader: '' as AccountInfo, + ref: '' as GitRef, + }, + }, + id: '' as ChangeInfoId, + project: '' as RepoName, + branch: '' as BranchName, + change_id: '' as ChangeId, + subject: '', + created: '' as Timestamp, + updated: '' as Timestamp, + insertions: 0, + deletions: 0, + _number: 0 as NumericChangeId, + owner: '' as AccountInfo, + reviewers: {}, }, + patchsetSha: '' }; // Fetch from firstChangeObject. @@ -1524,7 +1984,7 @@ fetcher.fetch(firstChangeObject); await new Promise(r => setTimeout(r, 100)); const firstController = fetcher.controllers.get(firstControllerKey); - assert.strictEqual(firstController.signal.aborted, false); + assert.strictEqual(firstController!.signal.aborted, false); // A fetch from secondChangeObject should not abort firstController. fetcher.fetch(secondChangeObject); @@ -1532,13 +1992,13 @@ const secondControllerKey = fetcher.getChangeDataId(secondChangeObject); const secondController = fetcher.controllers.get(secondControllerKey); assert.notStrictEqual(firstController, secondController); - assert.strictEqual(firstController.signal.aborted, false); - assert.strictEqual(secondController.signal.aborted, false); + assert.strictEqual(firstController!.signal.aborted, false); + assert.strictEqual(secondController!.signal.aborted, false); // Aborting secondChangeObject should not abort firstController. fetcher.fetch(secondChangeObject) await new Promise(r => setTimeout(r, 100)); - assert.strictEqual(firstController.signal.aborted, false); - assert.strictEqual(secondController.signal.aborted, true); + assert.strictEqual(firstController!.signal.aborted, false); + assert.strictEqual(secondController!.signal.aborted, true); }); });
diff --git a/web/test/checks-result_test.ts b/web/test/checks-result_test.ts index ea1fddf..bd38d29 100644 --- a/web/test/checks-result_test.ts +++ b/web/test/checks-result_test.ts
@@ -11,12 +11,12 @@ parseMarkdown, } from '../checks-result'; -function sanitizedAttr(attr) { +function sanitizedAttr(attr: string) { return `rel="noopener" target="_blank" ${attr}`; } suite('checks-result tests', () => { - let sandbox; + let sandbox: sinon.SinonSandbox; const defaultMetaAttr = 'alt="chromeChecksMetadataTagDefault"'; const greenMetaAttr = 'alt="chromeChecksMetadataTagGreen"'; const redMetaAttr = 'alt="chromeChecksMetadataTagRed"'; @@ -24,7 +24,7 @@ setup(() => { sandbox = sinon.createSandbox(); sandbox.stub(ResultDbV1Client.prototype, 'getAuthorizationHeader') - .returns({'authorization': 'accessToken'}); + .returns(Promise.resolve({'authorization': 'accessToken'})); }); teardown(() => { @@ -62,7 +62,7 @@ await installChecksResult(element); // Check element is styled. - const style = (element.getRootNode() as HTMLElement).querySelector('style').textContent; + const style = (element.getRootNode() as any).querySelector('style').textContent; assert.isTrue(style.includes('word-break: break-all;')); assert.isTrue(style.includes('white-space: pre-wrap;')); @@ -74,7 +74,7 @@ fetchStub.returns(Promise.resolve({ ok: true, text: async () => 'foo test failed!', - })); + } as Response)); const artifactStub = sandbox.stub( ResultDbV1Client.prototype, 'listArtifacts'); @@ -143,6 +143,7 @@ artifactId: 'baz', fetchUrl: 'https://baz.com', fetchUrlExpiration: '2017-12-15T01:30:15.05Z', + sizeBytes: 0, }, ], })); @@ -157,7 +158,7 @@ }], plugin: { checks: () => ({ - updateResult: (_1, _2) => {}, + updateResult: (_1: any, _2: any) => {}, }), }, rdbHost: 'host', @@ -191,7 +192,7 @@ ], plugin: { checks: () => ({ - updateResult: (_1, _2) => {}, + updateResult: (_1: any, _2: any) => {}, }), }, rdbHost: 'host', @@ -218,18 +219,18 @@ fetchStub.withArgs('https://foo.com/?n=50000').returns(Promise.resolve({ ok: true, text: async () => 'foo test failed!', - })); + } as Response)); fetchStub.withArgs('https://bar.org/?n=50000').returns(Promise.resolve({ ok: true, text: async () => 'bar test passed!', - })); + } as Response)); fetchStub.withArgs('https://baz.org/?n=50000').returns(Promise.reject( new Error('bad fetch'))); fetchStub.withArgs('https://boo.org/?n=50000').returns(Promise.resolve({ ok: false, status: 500, text: async () => 'server error', - })); + } as Response)); const resArtifacts = [ { @@ -281,7 +282,7 @@ fetchStub.onCall(1).returns(Promise.resolve({ ok: true, text: async () => 'foobar test failed!', - })); + } as Response)); fetchStub.onCall(2).returns(Promise.reject( new Error('bad fetch'))); @@ -295,23 +296,23 @@ // Replace and fetch. const html = '<p><text-artifact artifact-id="foobar" /></p>'; assert.strictEqual( - await replaceTextArtifacts(html, artifacts, null), + await replaceTextArtifacts(html, artifacts, []), `<p><a ${defaultMetaAttr}>Failed to fetch foobar artifact: bad fetch. See console logs.</a></p>`, ); assert.strictEqual( - await replaceTextArtifacts(html, artifacts, null), + await replaceTextArtifacts(html, artifacts, []), '<p>foobar test failed!</p>', ); sinon.assert.calledTwice((fetch as any)); // Check that fetches after the first successful fetch use cache. assert.strictEqual( - await replaceTextArtifacts(html, artifacts, null), + await replaceTextArtifacts(html, artifacts, []), '<p>foobar test failed!</p>', ); assert.strictEqual( - await replaceTextArtifacts(html, artifacts, null), + await replaceTextArtifacts(html, artifacts, []), '<p>foobar test failed!</p>', ); sinon.assert.calledTwice((fetch as any)); @@ -323,12 +324,12 @@ fetchStub.withArgs('https://a1.com/?n=50000').returns(Promise.resolve({ ok: true, text: async () => artifactOne, - })); + } as Response)); const artifactTwo = 'a'.repeat(50002); fetchStub.withArgs('https://a2.com/?n=50000').returns(Promise.resolve({ ok: true, text: async () => artifactTwo, - })); + } as Response)); const artifacts = [ { @@ -347,14 +348,14 @@ assert.strictEqual( await replaceTextArtifacts( - '<p><text-artifact artifact-id="a1"/></p>', artifacts, null), + '<p><text-artifact artifact-id="a1"/></p>', artifacts, []), `<p>${artifactOne}<a ${defaultMetaAttr}>\n\n...1 more byte...\n` + 'More information in a1 artifact</a></p>', ); assert.strictEqual( await replaceTextArtifacts( - '<p><text-artifact artifact-id="a2"/></p>', artifacts, null), + '<p><text-artifact artifact-id="a2"/></p>', artifacts, []), `<p>${artifactTwo}<a ${defaultMetaAttr}>\n\n...2 more bytes...\n` + 'More information in a2 artifact</a></p>', );
diff --git a/web/test/cr-tryjob-picker_test.ts b/web/test/cr-tryjob-picker_test.ts index 76b10cf..ad362cd 100644 --- a/web/test/cr-tryjob-picker_test.ts +++ b/web/test/cr-tryjob-picker_test.ts
@@ -5,7 +5,7 @@ import {CrTryjobPicker} from '../cr-tryjob-picker'; suite('cr-tryjob-picker basic tests', () => { - let sandbox; + let sandbox: sinon.SinonSandbox; let element: CrTryjobPicker; let closeFired = false; @@ -62,19 +62,27 @@ test('bucket filter function filters builders', () => { let fn = element.computeBucketFilterFn(_ => true); assert.isTrue(fn({ - builders: ['blerp', 'GERP'], - })); + builders: ['blerp', 'GERP'], + project: '', + bucket: '' +})); fn = element.computeBucketFilterFn(s => /gerp/.test(s)); assert.isTrue(fn({ - builders: ['blerp', 'gerp'], - })); + builders: ['blerp', 'gerp'], + project: '', + bucket: '' +})); assert.isTrue(fn({ - builders: ['gerp'], - })); + builders: ['gerp'], + project: '', + bucket: '' +})); assert.isFalse(fn({ - builders: ['blerp'], - })); + builders: ['blerp'], + project: '', + bucket: '' +})); }); test('filter compilation with literal string', () => { @@ -109,7 +117,7 @@ test('selectedBuilders updated on checkbox change', async () => { await element.bucketsUpdating; - const addButton = element.shadowRoot.querySelector('gr-button[primary]'); + const addButton = element.shadowRoot?.querySelector('gr-button[primary]'); const el = document.createElement('input'); el.type = 'checkbox'; el.setAttribute('data-project', 'proj.foo'); @@ -130,7 +138,7 @@ assert.equal(Object.keys(element.selectedBuilders).length, 1); assert.isTrue( element.computeChecked('proj.foo', 'bucket.bar', 'builder.baz')); - assert.equal(addButton.getAttribute("disabled"), ""); + assert.equal(addButton?.getAttribute("disabled"), ""); }); test('computeBuckets merges builders', async () => { @@ -199,22 +207,22 @@ }], }; await element.bucketsUpdating; - const addButton = element.shadowRoot.querySelector('gr-button[primary]'); - assert.isTrue(addButton.hasAttribute('disabled')); - const checkbox = element.shadowRoot.querySelector('input[type="checkbox"]'); + const addButton = element.shadowRoot?.querySelector('gr-button[primary]'); + assert.isTrue(addButton?.hasAttribute('disabled')); + const checkbox = element.shadowRoot?.querySelector('input[type="checkbox"]'); assert.ok(checkbox); const changePromise = new Promise((resolve) => { element.addEventListener('change', resolve); }); (checkbox as any).checked = true; - checkbox.dispatchEvent( + checkbox?.dispatchEvent( new CustomEvent('change', {bubbles: true, composed: true})); await changePromise; - assert.isFalse(addButton.hasAttribute('disabled')); + assert.isFalse(addButton?.hasAttribute('disabled')); }); test('includeTrybots updated when bots selected', async () => { - let fn = (project, bucket, builder, isChecked) => { + let fn = (project: string, bucket: string, builder: string, isChecked: boolean) => { const el = document.createElement('input'); el.type = 'checkbox'; el.setAttribute('data-project', project); @@ -266,12 +274,12 @@ builder: 'linux-rel', }, }; - sandbox.stub(BuildbucketV2Client.prototype, 'batch').returns({}); + sandbox.stub(BuildbucketV2Client.prototype, 'batch').returns(Promise.resolve({})); await element.onAddTap(); assert.isTrue(closeFired); assert.isFalse(element.disabled); assert.deepEqual(element.selectedBuilders, {}); - assert.isNull(element.clientOperationId); + assert.isUndefined(element.clientOperationId); }); test('on HTTP request error', async () => {
diff --git a/web/test/lru-cache-object_test.ts b/web/test/lru-cache-object_test.ts index 67fd038..a2fc571 100644 --- a/web/test/lru-cache-object_test.ts +++ b/web/test/lru-cache-object_test.ts
@@ -9,7 +9,7 @@ } from '../lru-storage-cache-object'; suite('LRUMemoryCacheObject', () => { - let cache; + let cache: LRUMemoryCacheObject; setup(() => { cache = new LRUMemoryCacheObject(2); @@ -65,8 +65,8 @@ }); suite('LRUStorageCacheObject', () => { - let sandbox; - let cache; + let sandbox: sinon.SinonSandbox; + let cache: LRUStorageCacheObject; const cacheKey = 'key'; setup(() => { @@ -90,11 +90,11 @@ test('write then clear then read yields empty cache', () => { cache.write('a', 1); const key = cache.formatKey('a'); - assert.deepEqual(JSON.parse(localStorage.getItem(key)), {value: 1}); + assert.deepEqual(JSON.parse(localStorage.getItem(key)!), {value: 1}); cache.clear(); assert.strictEqual(cache.read('a'), undefined); assert.deepEqual(cache.accessKeys, []); - assert.strictEqual(JSON.parse(localStorage.getItem(key)), null); + assert.strictEqual(JSON.parse(localStorage.getItem(key)!), null); }); test('delete deletes keys', () => { @@ -140,7 +140,7 @@ cache.write('b', 2); const smallerCache = new LRUStorageCacheObject(1, cacheKey); assert.strictEqual(smallerCache.read('a'), undefined); - assert.strictEqual(JSON.parse(localStorage.getItem('a')), null); + assert.strictEqual(JSON.parse(localStorage.getItem('a')!), null); assert.strictEqual(smallerCache.read('b'), 2); smallerCache.clear();
diff --git a/web/test/resultdb-client_test.ts b/web/test/resultdb-client_test.ts index 8cad9c0..aec1c69 100644 --- a/web/test/resultdb-client_test.ts +++ b/web/test/resultdb-client_test.ts
@@ -1,14 +1,17 @@ +import { BuildStatus } from '../buildbucket-client'; +import { Build } from '../checks-fetcher'; import { ResultDbV1Client, + TestVariantStatus, } from '../resultdb-client'; suite('ResultDbV1Client tests', () => { - let sandbox; - let client; + let sandbox: sinon.SinonSandbox; + let client: ResultDbV1Client; setup(() => { sandbox = sinon.createSandbox(); client = new ResultDbV1Client('resultdb.example.com', '123456'); - client.getAuthorizationHeader = async () => ({'authroization': 'access_token'}); + client.getAuthorizationHeader = async () => ({'authorization': 'access_token'}); }); teardown(() => { @@ -17,24 +20,29 @@ test('fetchTestVariants caches both additional and non-additional variants', async () => { - const build = { + const build: Build = { id: '123', endTime: '2017-12-15T01:30:15.05Z', infra: { resultdb: { + hostname: '', invocation: 'foo' } }, + builder: {}, + status: BuildStatus.SCHEDULED, + createTime: '', + startTime: '' }; const fetch = sandbox.stub(window, 'fetch'); fetch.onCall(0).returns(Promise.resolve({ ok: true, text: async () => `)]}'${JSON.stringify({testVariants: []})}`, - })); + } as Response)); fetch.onCall(1).returns(Promise.resolve({ ok: true, - text: async () => `)]}'${JSON.stringify({testVariants: [{status: 'FLAKY'}]})}`, - })); + text: async () => `)]}'${JSON.stringify({testVariants: [{status: 'FLAKY', variant: '', results: [], exonerations: []}]})}`, + } as Response)); // inclAdditional is false. assert.deepEqual( @@ -45,7 +53,7 @@ // inclAdditional is true. assert.deepEqual( await client.fetchTestVariants(build, 1, true), - [{status: 'FLAKY'}], + [{status: TestVariantStatus.FLAKY, variant: '', results: [], exonerations: []}], ); sinon.assert.calledTwice(fetch); @@ -57,7 +65,7 @@ ); assert.deepEqual( await client.fetchTestVariants(build, 1, true), - [{status: 'FLAKY'}], + [{status: TestVariantStatus.FLAKY, variant: '', results: [], exonerations: []}], ); sinon.assert.calledTwice(fetch); }); @@ -68,12 +76,18 @@ () => ({ name: 'invocations/123/result/artifacts/foo', fetchUrlExpiration: '2017-12-15T01:30:15.05Z', + artifactId: '', + fetchUrl: '', + sizeBytes: 0, })), }; const secondPage = { artifacts: [{ name: 'invocations/123/artifacts/foo', fetchUrlExpiration: '2017-12-15T01:30:15.05Z', + artifactId: '', + fetchUrl: '', + sizeBytes: 0, }], }; @@ -81,11 +95,11 @@ fetch.onCall(0).returns(Promise.resolve({ ok: true, text: async () => `)]}'${JSON.stringify(firstPage)}`, - })); + } as Response)); fetch.onCall(1).returns(Promise.resolve({ ok: true, text: async () => `)]}'${JSON.stringify(secondPage)}`, - })); + } as Response)); assert.deepEqual( await client.fetchArtifacts('invocations/123/result', true), @@ -102,29 +116,35 @@ const fooArtifacts = [{ name: 'invocations/123/artifacts/foo', fetchUrlExpiration: '2999-12-15T01:30:15.05Z', + artifactId: '', + fetchUrl: '', + sizeBytes: 0, }]; const barArtifacts = [{ name: 'invocations/123/artifacts/bar', fetchUrlExpiration: '2999-12-15T01:30:15.05Z', + artifactId: '', + fetchUrl: '', + sizeBytes: 0, }]; const fetch = sandbox.stub(window, 'fetch'); fetch.onCall(0).returns(Promise.resolve({ ok: true, text: async () => `)]}'${JSON.stringify({artifacts: fooArtifacts})}`, - })); + } as Response)); fetch.onCall(1).returns(Promise.resolve({ ok: true, text: async () => `)]}'${JSON.stringify([])}`, - })); + } as Response)); fetch.onCall(2).returns(Promise.resolve({ ok: true, text: async () => `)]}'${JSON.stringify({artifacts: barArtifacts})}`, - })); + } as Response)); fetch.onCall(3).returns(Promise.resolve({ ok: true, text: async () => `)]}'${JSON.stringify([])}`, - })); + } as Response)); assert.deepEqual(await client.fetchArtifacts('invocations/123/foo', true), {
diff --git a/web/test/test-util.ts b/web/test/test-util.ts index 81a9c3e..6e8ec0d 100644 --- a/web/test/test-util.ts +++ b/web/test/test-util.ts
@@ -10,16 +10,16 @@ * If RegExp or string, then rejection reason is expected to have message * property (like Error) that matches the expected reason. */ -export async function assertRejects(promise, expectedReason = null) { +export async function assertRejects(promise: any, expectedReason: any = null) { try { await promise; assert.fail('exception', 'no exception'); - } catch (actualReason) { + } catch (actualReason: unknown) { if (expectedReason) { if (expectedReason instanceof RegExp) { - assert.match(actualReason.message, expectedReason); + assert.match((actualReason as Error).message, expectedReason); } else if (typeof expectedReason == 'string') { - assert.equal(actualReason.message, expectedReason); + assert.equal((actualReason as Error).message, expectedReason); } else { assert.deepEqual(actualReason, expectedReason); } @@ -27,7 +27,7 @@ } } -export function deepFreeze(obj) { +export function deepFreeze(obj: any) { for (const p of Object.keys(obj)) { const v = obj[p]; if (typeof v == 'object' && v !== null) {
diff --git a/web/tsconfig.json b/web/tsconfig.json index f1f2447..05cd7ec 100644 --- a/web/tsconfig.json +++ b/web/tsconfig.json
@@ -3,9 +3,6 @@ "compilerOptions": { /* outDir for IDE (overridden by Bazel rule arg) */ "outDir": "../../../.ts-out/plugins/buildbucket/web", - "noImplicitAny": false, - "strictNullChecks": false, - "strictPropertyInitialization": false, "lib": ["dom", "dom.iterable", "es2021"] }, "include": [