// Copyright 2021 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import * as fs from 'fs'; import * as http from 'http'; // This type mirrors test_result.proto. // https://source.chromium.org/chromium/infra/infra/+/main:recipes-py/recipe_proto/go.chromium.org/luci/resultdb/proto/sink/v1/test_result.proto export interface TestResult { testId: SanitizedTestId; expected?: boolean; status?: 'PASS'|'FAIL'|'SKIP'; summaryHtml?: string; duration?: string; tags?: {key: string, value: string}[]; } class SanitizedTestIdTag { private sanitizedTag: (string|undefined); } export type SanitizedTestId = string&SanitizedTestIdTag; // ResultSink checks the testId against the regex /^[[print]]{1,512}$/: // https://source.chromium.org/chromium/infra/infra/+/main:go/src/go.chromium.org/luci/resultdb/pbutil/test_result.go;l=43;drc=7ba090da753a71be5a0f37785558e9102e57fa10 // // This function removees non-printable characters and truncates the string // to the max allowed length. export function sanitizedTestId(rawTestId: string): SanitizedTestId { return rawTestId.replace(/[^\x20-\x7E]/g, '').substr(0, 512) as SanitizedTestId; } let collectedTestResults: TestResult[] = []; export function recordTestResult(testResult: TestResult): void { collectedTestResults.push(testResult); } // Call at the end of a test suite. Will send all `TestResult`s collected via // `recordTestResult` to the ResultSink endpoint (only if available). export function sendCollectedTestResultsIfSinkIsAvailable() { if (!process.env.LUCI_CONTEXT || !fs.existsSync(process.env.LUCI_CONTEXT)) { return; } const luciConfig = fs.readFileSync(process.env.LUCI_CONTEXT, 'utf8'); const sink = JSON.parse(luciConfig)['result_sink']; // LUCI_CONTEXT will not have a result_sink configuration when // ResultSink is unavailable. if (!sink) { return; } const url = `http://${sink.address}/prpc/luci.resultsink.v1.Sink/ReportTestResults`; const postOptions = { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': `ResultSink ${sink.auth_token}`, }, }; // As per ResultSink documentation, this will always be a localhost connection // and can be treated as reliable as a local file write. const request = http.request(url, postOptions, () => {}); const data = JSON.stringify({testResults: collectedTestResults}); request.write(data); request.end(); collectedTestResults = []; }